最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Javascript异步的发展与6种解决方案总结

    正文概述 掘金(YanniLi)   2020-12-10   395

    异步(Asynchronous)指同一时间不止一个事件发生,或者说是多个相关事件不等待前一事件完成就发生。异步处理不用阻塞当前线程来等待处理完成,而是允许后续操作,直至其它线程将处理完成,并回调通知此线程。

    现在与将来

    一个完整的javascript程序,几乎一定是由多个块构成的。这些块中只有一个是现在执行,其余的则会在将来执行。最常见的块单位是函数。

    举个例子

    function now() {
     return 21;
    }
    function later() {
     answer = answer * 2;
     console.log( "Meaning of life:", answer );
    }
    var answer = now();
    setTimeout( later, 1000 );
    

    上面的例子两个块:现在执行的部分,以及将来执行的部分。

    现在执行部分

    function now() {
     return 21;
    }
    function later() { .. }
    var answer = now();
    setTimeout( later, 1000 );
    
    

    将来执行部分

    answer = answer * 2;
    console.log( "Meaning of life:", answer );
    

    事件循环

    异步与事件循环密切相关,在了解解决方案前,建议先看下并发模型与事件循环。

    异步编程解决方案

    • 回调
    • 事件监听
    • 发布订阅
    • Promise
    • Generator
    • async/await

    注意:Promise、Generator、async/await 在IE浏览器都不支持,需要做兼容处理。

    下面介绍常用的解决方案。

    1.回调

    JavaScript 语言对异步编程的实现,就是回调函数。所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。

    缺点:大量的嵌套回调会造成回调地狱,难以维护。

    // 如果下一请求依赖上一个请求的返回值,就需要不断嵌套
    $.ajax({ 
      url: "url-1", 
      success: function(){
        $.ajax({ 
          url: "url-2", 
          success: function(){
            $.ajax({ 
              url: "url-3", 
              success: function(){
                // ...
              }
            });
          }
        });
      }
    });
    

    2.promise(ES6)

    介绍

    • promise是一个代表了异步操作最终完成或者失败的对象。
    • 本质上,promise是一个函数返回的对象, 它可以绑定回调函数
    • Promise对象是一个构造函数,用来生成Promise实例。
    • 使用promise最直接的好处就是能够使用then进行链式调用

    创建

    Promise 对象是由关键字 new 及其构造函数来创建的。

    var p = new Promise((resolve, reject) => {
        // 一系列异步操作
        // resolve()
        // reject()
    });
    console.log(p) // Promise
    

    想要某个函数拥有 promise 功能,只需让其返回一个promise即可。

    function myAsyncFunction(url) {
        return new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest();
            xhr.open("GET", url);
            xhr.onload = () => resolve(xhr.responseText);
            xhr.onerror = () => reject(xhr.statusText);
            xhr.send();
        });
      };
    

    状态

    一个 Promise有以下几种状态:

    • pending: 初始状态,既不是成功,也不是失败状态。
    • fulfilled: 意味着操作成功完成。状态:pengding=>fulfilled
    • rejected: 意味着操作失败。状态:pending=>rejected
    var p1 = new Promise((resolve, reject) => {
    });
    console.log(p1); // pending
    
    var p2 = new Promise((resolve, reject) => {
        resolve('成功');
    });
    console.log(p2); // fulfilled
    
    var p3 = new Promise((resolve, reject) => {
        reject('失败');
    });
    console.log(p3); // reject
    

    注意:promise状态是不可逆的。

    promise的状态 只有从pengding =》fulfilled 或者 pending =》 rejected,而不会反过来。并且已经resolve的数据,后面无论如何修改,都不会改变then中接受到的数据。

    new Promise((resolve, reject) => {
        var num = 100;
        resolve(num);
        num = 999;
        resolve(num); // resolve 也不会改变已传出去的num 100
        console.log(num) // 999
    }).then(result => { 
        console.log(result) // 100
    });
    

    属性

    • Promise.length:length属性,值总是为1
    • Promise.prototype:构造器原型

    方法

    iterable:一个可迭代对象,如 Array 或 String。

    方法名功能返回结果
    Promise.all(iterable)所有传入的 promise 成功才触发成功,只要有一个失败就会触发失败包含所有 promise 值的数组Promise.allSettled(iterable)所有传入的promise都完成(成功/失败)后完成包含所有 promise 值的数组Promise.any(iterable)当第一个成功的 promise 成功时返回第一个成功的promise的值Promise.race(iterable)当第一个 promise 成功/失败返回第一个完成的promise的值Promise.reject(reason)返回一个状态为失败的 Promise 对象状态为失败的PromisePromise.resolve(value)返回一个状态由给定value决定的 Promise对象状态为成功的Promise

    原型属性

    • Promise.prototype.constructor:返回被创建的实例函数. 默认为 Promise 函数.

    原型方法

    方法名功能返回结果说明
    Promise.prototype.then(resolFun, rejecFun)添加 fulfilled 和 rejected 回调到当前 promise返回新的 promise当回调函数被调用,新 promise 都将以它的返回值来resolvePromise.prototype.catch(Fun)添加一个 rejection 回调到当前 promise返回新的 promise返回的 promise 会以 rejection 的返回值 resolvePromise.prototype.finally(Fun)当其中的一个 promise 成功时返回返回新的 promise无论是fulfilled还是rejected,都会执行

    promise/A+规范

    Promise 规范有很多,如Promise/A,Promise/B,Promise/D 以及 Promise/A 的升级版 Promise/A+。

    ES6 中采用了 Promise/A+ 规范。

    Promises/A+规范总结:

    1. 一个 promise 的当前状态只能是 pending、fulfilled 和 rejected 三种之一。状态改变只能是 pending 到 fulfilled 或者 pending 到rejected。状态改变不可逆。
    2. promise 的 then 方法接收两个可选参数,表示该 promise 状态改变时的回调。then 方法必须返回一个 promise。then 方法可以被同一个 promise 调用多次。

    3.Generator(ES6)

    介绍

    • Generator 函数是一个状态机,封装了多个内部状态
    • 执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态
    • Generator 函数只有调用 next() 方法才会遍历下一个内部状态

    关键标识和关键字

    • function* : 关键标识
    • yield:暂停执行
    • yield* :语法糖,在 Generator 函数中执行另一个 Generator 函数

    方法

    方法名功能备注
    Generator.prototype.next()返回一个由 yield表达式生成的值Generator.prototype.return()返回给定的值并结束生成器Generator.prototype.throw()向生成器抛出一个错误

    下面对关键字和各个方法做详细介绍

    yield 表达式

    • yield 表示暂停执行
    • yield 表达式后面的表达式,只有当调用 next()、内部指针指向该语句时才会执行
    • yield 表达式的值会作为返回的对象的 value 属性值
    • 调用 next() 之前,yield 前面的语句不会执行
    function* helloWorldGenerator() {
      console.log('aaa')
      yield 'hello';
      console.log('bbb')
      yield 'world';
      console.log('ccc')
      return 'ending';
    }
    
    var hw = helloWorldGenerator();
    console.log(hw); // helloWorldGenerator {<suspended>}  状态:suspended
    
    hw.next()
    // aaa { value: 'hello', done: false }
    
    hw.next()
    // bbb { value: 'world', done: false }
    
    hw.next()
    // ccc { value: 'ending', done: true }
    
    hw.next()
    // { value: undefined, done: true }
    
    console.log(hw); // helloWorldGenerator {<closed>}   状态:closed
    

    使用注意:

    • yield 只能在 Generator 函数中使用,在其他地方使用会报错。就算是在Generator 函数内,但处于一个普通函数内,也会报错
    var arr = [1, [[2, 3], 4], [5, 6]];
    
    var flat = function* (a) {
      a.forEach(function (item) {
        if (typeof item !== 'number') {
          yield* flat(item);
        } else {
          yield item;
        }
      });
    };
    
    for (var f of flat(arr)){
      console.log(f);
    }
    // Uncaught SyntaxError: Unexpected identifier
    
    // 改造下
    var arr = [1, [[2, 3], 4], [5, 6]];
    
    var flat = function* (a) {
      var length = a.length;
      for (var i = 0; i < length; i++) {
        var item = a[i];
        if (typeof item !== 'number') {
          yield* flat(item);
        } else {
          yield item;
        }
      }
    };
    
    for (var f of flat(arr)) {
      console.log(f);
    }
    // 1, 2, 3, 4, 5, 6
    
    • yield表达式如果用在另一个表达式之中,必须放在圆括号里面
    function* demo() {
      console.log('Hello' + yield); // SyntaxError
      console.log('Hello' + yield 123); // SyntaxError
    
      console.log('Hello' + (yield)); // OK
      console.log('Hello' + (yield 123)); // OK
    }
    
    • yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号
    function* demo() {
      foo(yield 'a', yield 'b'); // OK
      let input = yield; // OK
    }
    

    next()方法

    • next() 表示恢复执行
    • next() 可接受参数,并且该参数表示上一个yield表达式的返回值
    • 第一次调用 next() 时,传递参数无效。(可封装函数,先执行一次无参 next())
    function* G() {
        const a = yield 100
        console.log('a', a)
        const b = yield 200
        console.log('b', b)
        const c = yield 300
        console.log('c', c)
    }
    var g = G()
    
    g.next(); // {value: 100, done: false}
    g.next(); // a undefined  {value: 200, done: false}
    g.next(); // b undefined  {value: 300, done: false}
    g.next(); // c undefined  {value: undefined, done: true}
    
    g.next();      // {value: 100, done: false}
    g.next('aaa'); // a aaa   {value: 200, done: false} 
    g.next('bbb'); // b bbb   {value: 300, done: false}
    g.next('ccc'); // c ccc   {value: undefined, done: true}
    

    throw()方法

    throw() 方法用来向生成器抛出异常,并恢复生成器的执行,返回带有 done 及 value 两个属性的对象。

    使用

    • throw 方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获
    • 如果 Generator 函数内部没有部署try...catch代码块,那么throw方法抛出的错误,将被外部try...catch代码块捕获
    • 如果 Generator 函数内部和外部,都没有部署try...catch代码块,那么程序将报错,直接中断执行
    • throw 方法可以接受一个参数,该参数会被catch语句接收
    • catch 语句只能捕获到第一个 throw 方法
    • throw 方法不会中断程序执行,并且会自动执行下一次 next()

    优点

    多个yield表达式,可以只用一个try...catch代码块来捕获错误

    var g = function* () {
      try {
        yield;
      } catch (e) {
        console.log('内部捕获', e); 
      }
    };
    
    var i = g();
    i.next();
    
    try {
      var err = i.throw('参数 aaa');
      console.log(err)
      i.throw('b');
    } catch (e) {
      console.log('外部捕获', e);
    }
    // 内部捕获 参数 aaa
    // {value: undefined, done: true}
    // 外部捕获 b
    

    如果 Generator 函数内部没有部署try...catch代码块,那么throw方法抛出的错误,将被外部try...catch代码块捕获

    var g = function* () {
      yield;
      console.log('内部捕获', e);
    };
    
    var i = g();
    i.next();
    
    try {
      i.throw('参数 aaa');
      i.throw('b');
    } catch (e) {
      console.log('外部捕获', e); // 外部捕获 参数 aaa
    }
    

    如果 Generator 函数内部和外部,都没有部署try...catch代码块,那么程序将报错,直接中断执行。

    var g = function* () {
      yield;
      console.log('内部捕获', e);
    };
    
    var i = g();
    i.next();
    i.throw();
    // VM9627:2 Uncaught undefined
    

    throw 方法不会中断程序执行,并且会自动执行下一次 next()

    var gen = function* gen(){
      try {
        yield console.log('a');
      } catch (e) {
        console.log('error')
      }
      yield console.log('b');
      yield console.log('c');
    }
    
    var g = gen();
    g.next() // a
    g.throw() // error b
    g.next() // c
    

    return()方法

    return方法可以返回给定的值,并且终结遍历 Generator 函数。

    • 如果 return 方法调用时,不提供参数,则返回值的 value 属性值为 undefined
    • 如果 Generator 函数内部有try...finally代码块,且正在执行try代码块,那么return方法会立刻进入finally代码块,执行完以后,整个函数才会结束。
    function* gen() {
      yield 1;
      yield 2;
      yield 3;
    }
    
    var g = gen();
    
    g.next()        // { value: 1, done: false }
    g.return('foo') // { value: "foo", done: true } =》后面的 done 始终为 true
    g.next()        // { value: undefined, done: true }
    

    Generator 函数内部有try...finally代码块

    function* numbers () {
      yield 1;
      try {
        yield 2;
        yield 3;
      } finally { // return后依然执行
        yield 4;
        yield 5;
      }
      yield 6;
    }
    var g = numbers();
    g.next() // { value: 1, done: false }
    g.next() // { value: 2, done: false }
    g.return(7) // { value: 4, done: false }
    g.next() // { value: 5, done: false }
    g.next() // { value: 7, done: true }
    

    yield* 表达式

    yield* 相当于一个语法糖(给后面的可迭代对象部署一个for...of循环),用于执行 Generator 函数内的 Generator 函数

    • yield* 等同于在 Generator 函数内部,部署一个for...of循环
    • yield* 返回一个遍历器对象
    • 任何可迭代对象都可被 yield* 遍历
    // 执行 Generator 函数内的 Generator 函数
    function* foo() {
      yield 2;
      yield 3;
      return "foo";
    }
    function* bar() {
      yield 1;
      var v = yield* foo();
      console.log("v: " + v);
      yield 4;
    }
    var it = bar();
    it.next()
    // {value: 1, done: false}
    it.next()
    // {value: 2, done: false}
    it.next()
    // {value: 3, done: false}
    it.next();
    // "v: foo"
    // {value: 4, done: false}
    it.next()
    // {value: undefined, done: true}
    
    
    // 任何可迭代对象都可被 yield* 遍历
    let read = (function* () {
      yield* 'he';
      yield* [1, 2, 3];
    })();
    
    read.next().value // "h"
    read.next().value // "e"
    read.next().value // 1
    read.next().value // 2
    read.next().value // 3
    

    应用

    待更新...

    4.async/await(ES7)

    介绍

    • async 函数是 Generator 函数的语法糖,它的写法更趋向于同步。
    • async 函数返回一个 promise 对象
    • async 函数可以包含0个或者多个 await 指令
    • await 会暂停异步函数的执行,并等待Promise执行,然后继续执行异步函数,并返回结果
    • await 表达之后的代码可以被认为是存在在链式调用的 then 回调方法中

    async对Generator的改进

    改进点asyncGenerator
    内置执行器调用即执行调用 next() 方法才执行更好的语义async(内部有异步操作)、await(等待异步操作完成后)Generator(生成器)、yield(产出)更广的适用性await 后可以是Promise 对象和原始数据类型(会被自动转成Promise.resolve()的值)yield命令后面只能是 Thunk 函数或 Promise 对象返回值是 Promise返回 Promise 对象返回 Iterator 对象

    基本使用

    // 异步请求
    function resolveAfter1Seconds() {
      return new Promise(resolve => {
        setTimeout(() => {
          resolve('1s resolved');
        }, 1000);
      });
    }
    function resolveAfter3Seconds() {
      return new Promise(resolve => {
        setTimeout(() => {
          resolve('3s resolved');
        }, 3000);
      });
    }
    
    
    async function asyncCall() {
      console.log('calling');
      const result1 = await resolveAfter3Seconds();
      console.log(result1)
      const result2 = await resolveAfter1Seconds();
      console.log(result2);
      return 'ending'
    }
    
    asyncCall().then(res => {
      console.log(res)
    });
    // calling
    // 3s resolved => 执行3秒后输出
    // 1s resolved => 执行4秒后输出
    // ending
    

    await命令

    • await 命令后是一个 Promise 对象,返回该对象的结果,如果不是则直接返回对应值
    • await 命令后的代码可以被认为是存在在链式调用的then回调方法中
    async function foo() {
       await 1 // 原始数据类型会被自动转成Promise.resolve()的值
    }
    // 等价于
    function foo() {
       return Promise.resolve(1).then(() => undefined)
    }
    
    

    错误处理

    • await 命令后代码执行失败,并且有 try...catch 捕捉错误,则不会影响接下来的代码执行。
    function step(val){
      if (val === 2) {
        return Promise.reject('出错了')
      } else {
        return  val
      }
    }
    
    async function main() {
      try {
        const val1 = await step(1);
        const val2 = await step(2);
        const val3 = await step(3);
        console.log('Final: ', val1, val2, val3);
      } catch (err) {
        console.error('error', err);
      }
      console.log('继续执行')
    }
    main();
    // error 出错了
    // 继续执行
    
    • await 命令没有 try...catch, async 函数返回的 Promise 对象会被reject,程序中断
    function step(val){
      if (val === 2) {
        return Promise.reject('出错了')
      } else {
        return  val
      }
    }
    
    async function main() {
      const val1 = await step(1);
      const val2 = await step(2);
      const val3 = await step(3);
      console.log('继续执行')
    }
    
    main().then().catch(err => {
      console.log('error', err)
    })
    // error 出错了
    

    async 函数的实现原理

    async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里

    async function fn(args) {
      // ...
    }
    
    // 等同于
    
    function fn(args) {
      return spawn(function* () {
        // ...
      });
    }
    
    function spawn(genF) {
      return new Promise(function(resolve, reject) {
        const gen = genF();
        function step(nextF) {
          let next;
          try {
            next = nextF();
          } catch(e) {
            return reject(e);
          }
          if(next.done) {
            return resolve(next.value);
          }
          Promise.resolve(next.value).then(function(v) {
            step(function() { return gen.next(v); });
          }, function(e) {
            step(function() { return gen.throw(e); });
          });
        }
        step(function() { return gen.next(undefined); });
      });
    }
    

    总结

    1. JS 异步编程进化史:callback -> promise -> generator -> async + await
    2. async/await 的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。
    3. async/await 相对于Promise,优势体现在:
      • 处理 then 的调用链,能够更清晰准确的写出代码并且也能优雅地解决回调地狱问题。
    4. async/await 对 Generator 函数的改进,体现在:
      • 内置执行器
      • 更好的语义
      • 更广的适用性
      • 返回值是 Promise 对象
    5. async/await 的缺点:
      • 如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低。代码没有依赖性的话,可以使用 Promise.all 的方式替代。

    起源地下载网 » Javascript异步的发展与6种解决方案总结

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元