2

之前看koa文档是koa是洋葱模型,里面用到的await,next。话不多说,直接贴图吧

实际await就是这样的执行逻辑。
先看一段代码:

      async function async1() {
        console.log("async1 start");
        await async2();
        console.log("async1 end");
      }
      async function async2() {
        console.log("async2");
        await async3()
      }
      async function async3() {
        await async4()
        console.log('async3')
      }
      async function async4() {
        await async5()
        console.log('async4')
      }
      async function async5() {
        console.log('async5')
      }
      async1();
      console.log('start')
      // 执行结果
      // async1 start
      // async2
      // async5
      // start
      // async4
      // async3
      // async1 end

分析下为何是这样的:
前面执行结果其实都很容易写出来
async1 start
async2
遇见await async2,那么紧跟在await async2 后面的那些代码就会延迟执行了。
有人说,因为await是promise的语法糖,所以实际紧跟await后面的代码可以理解成
new Promise().then(的回调函数来执行)
鹅,,,可能是这样的,或许这样也好记好理解一些。
还是按照洋葱模型来理解执行顺序吧。
第一个await async2 后面的代码先放着,一会再回来执行
ps:什么时候再回来执行呢?

答曰:等到没有遇到新的await后,并且非await的同步代码也执行完了后
所以执行顺序是:
await async2 --> await async3 --> await async4 --> await async5
-->执行非await的同步代码
-->反过来执行await async5紧跟后面的被阻塞的代码
...
-->反过来执行await async2紧跟后面的被阻塞的代码

其实这个例子,是await 函数不停的嵌套await,所以用这个例子,理解洋葱模型最好了。

再看一段结合setTimeout异步的代码

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
  setTimeout(() => {
    console.log('timer1')
  }, 0)
}
async function async2() {
  setTimeout(() => {
    console.log('timer2')
  }, 0)
  console.log("async2");
}
async1();
setTimeout(() => {
  console.log('timer3')
}, 0)
console.log("start")
// 执行结果
// 'async1 start'
// 'async2'
// 'start'
// 'async1 end'
// 'timer2'
// 'timer3'
// 'timer1'

这里setTimeout都延迟了0秒,所以按照eventLoop的宏任务,按照宏队列循环的先后来执行。
执行await async2()时,内部有一个timer2的setTimeout,所以列为宏1,
执行同步代码时候,遇见一个timer3的setTimeout,列为宏2,
按照await紧跟在后面的代码的洋葱执行逻辑,timer1,列为宏3

所以setTimeout的执行顺序就是:
time2-->timer3-->timer1

再看一个await的例子:

      async function testSometing() {
        console.log("执行testSometing");
        return "testSometing";
      }

      async function testAsync() {
        console.log("执行testAsync");
        return Promise.resolve("hello async");
      }

      async function test() {
        console.log("test start...");
        const v1 = await testSometing();
        console.log(v1);
        const v2 = await testAsync();
        console.log(v2);
        console.log(v1, v2);
      }

      test();

      var promise = new Promise(resolve => {
        console.log("promise start...");
        resolve("promise");
      });
      promise.then(val => console.log(val));
      console.log("test end...");
     // 打印结果
     // test start...
     // 执行testSometing
     // promise start...
     // test end...
     // testSometing
     // 执行testAsync
     // promise
     // hello async
     // testSometing hello async

上面例子一开始我答案写错了,我用第一道题的await 嵌套去解题了。
后来又去做了一次,还是做错了一部分。
在promise.then 和console.log(v2) 和 console.log(v1,v2)顺序上出错了。

后来搜索了相关文章,类似promise.then 和await后的代码执行前后顺序问题,不同的node版本,以及不同的chrome浏览器表现并不相同。
有打印出认为await后的同步任务先执行
也有打印出promise.then后面的微任务先执行

其实回过头想,只要记住,函数内部紧跟await后面的语句都是阻塞的就可以了。
再看一个例子:

     async testAwait () {
      async function async1 () {
        console.log('async1 start');
        await new Promise((resolve, reject) => {
          console.log('promise1')
          resolve(2)
        })
        console.log('async1 success');
        return 'async1 end'
      }
      console.log('srcipt start')
      await async1()
      console.log('srcipt end')
    }
    // 打印结果
    // srcipt start
    // async1 start
    // promise1
    // async1 success
    // srcipt end

但如果去掉Promise 里的resolve,执行结果如下

// srcipt start
// async1 start
// promise1

为何呢?
因为await async1(),如果async1内部没有被resolve,或内部发生了报错,await async1 后面的代码都会被阻塞掉。
(内部报错,或者reject的话,编译后的await函数会内部报错,可以看打印结果的)

如果把await async1()换成
async1().then(res => console.log(res))
那么同步代码
console.log('srcipt end')
不会被影响,因为async1()返回了一个Promise,但状态一直是pendding,所以then无法进去。

所以日常在写业务代码的时候,务必保证调用的await 函数,他内部状态,不然会影响整个js同步进程的执行,造成阻塞。

参考:阮一峰await async


健儿
79 声望4 粉丝

掌握好原生js。