乐趣区

关于前端:过程详解async-await综合题

前言

如果你之前跟我一样始终对async`await` 相熟又生疏的话(相熟是可能每天都在用,生疏是针对一些组合题又丈二和尚摸不着头脑),无妨能够边看边练,总结法则,置信会逐步清晰并有所得。本文对每个案例都详细描述了代码的执行流程,如有不妥欢送斧正。

async 函数 return 值

async函数默认会返回一个 Promise 对象,不论最初函数有没有 return 值。然而针对具体的返回值状况,实际上体现会有所不同,上面别离看看。

return 值为一般值

这里的一般值是指根底类型值(NumberStringBoolean 等)和非 thenable 和非 Promise 的值

async function foo() {return 'foo'}
foo().then(() => console.log('a'))
Promise.resolve()
  .then(() => console.log('b'))
  .then(() => console.log('c'))
// 输入后果:a b c

很简略,不出意外输入 a b c,也就是async 函数在执行实现后是没有期待的。

  1. foo()执行实现没有期待,遇到 thenconsole.log('a')放入微工作队列;
  2. 持续往下执行 Promise.resolve(),遇到thenconsole.log('b')入队,以后同步工作全副执行实现;
  3. 开始执行微工作队列,首先取出并执行 console.log('a') 输入a
  4. 而后取出并执行 console.log('b') 输入 b,此时遇到thenconsole.log('c')入队;
  5. 最初取出并执行 console.log('c') 输入c,至此微工作队列清空,代码执行完结;

    return 值为 thenable

    所谓值为 thenable 是指定义了 then 办法的对象,能够是一个字面对象,也能够是一个 Class 实例。

    class Bar {then(resolve) {resolve()
     console.log('then')
      }
    }
    
    async function foo() {// return new Bar()
      return {then(resolve, reject) {resolve()
       console.log('then')
     }
      }
    }
    foo().then(() => console.log('a'))
    Promise.resolve()
      .then(() => console.log('b'))
      .then(() => console.log('c'))
    // 输入后果:then b a c

    怎么程序不一样了呢?

    如果 async 函数的返回值是一个 thenable,等同于生成一个Promise,在foo 函数执行实现,并且 Promise 状态变更(resolve或者 reject)后,还要等 1 个then 的时长

  6. foo()返回 thenable 值,执行 then 办法,Promise状态变更,执行 console.log('then') 输入 then,期待 1 个then 时长;
  7. 持续往下执行 Promise.resolve(),遇到thenconsole.log('b')放入微工作队列,以后同步工作执行实现;
  8. 开始执行微工作队列,首先取出并执行 console.log('b') 输入b,以后微工作队列清空;
  9. 此时步骤 1 期待时长到期,遇到 thenconsole.log('a')放入队列,取出执行输入a
  10. 持续步骤 3 遇到 thenconsole.log('c')放入队列,取出执行输入c,至此微工作队列清空,代码执行完结;

这里如果 foo 函数返回的 thenable 办法的状态没有变更,则前面的 foo().then 将永远不会执行。

async function foo() {
  return {then(resolve, reject) {console.log('then')
    }
  }
}
foo().then(() => console.log('a'))
Promise.resolve()
  .then(() => console.log('b'))
  .then(() => console.log('c'))
// 输入后果:then b c

return 值为 Promise

return前面的值是 Promise,比方 new Promise(resolve=>resolve())Promise.resolve

async function foo() {return Promise.resolve('foo')
}
foo().then(() => console.log('a'))
Promise.resolve()
  .then(() => console.log('b'))
  .then(() => console.log('c'))
  .then(() => console.log('d'))

// 输入后果:b c a d

显著能够看出 async 函数执行完后提早了 2 个 then 时长。

  1. foo()返回 Promise 值,Promise状态变更,期待 2 个 then 时长;
  2. 持续往下执行 Promise.resolve(),遇到thenconsole.log('b')放入微工作队列,以后同步工作执行实现;
  3. 开始执行微工作队列,首先取出并执行 console.log('b') 输入b,以后微工作队列清空;
  4. 遇到 thenconsole.log('c')放入队列,取出执行输入c
  5. 此时步骤 1 期待时长到期,遇到 thenconsole.log('a')放入队列,取出执行输入a
  6. 持续步骤 4 遇到 thenconsole.log('d')放入队列,取出执行输入d,至此微工作队列清空,代码执行完结;

综合上述体现能够总结出如下法则

await 表达式值

既然 async 函数返回值对代码执行程序有影响,那么 await 前面的表达式值是否也有影响呢?上面同样分为上述三种场景进行试验剖析

await 值为一般值

async function foo() {
  await 'foo'
  console.log('a')
}
foo().then(() => console.log('b'))
Promise.resolve()
  .then(() => console.log('c'))
  .then(() => console.log('d'))
// 输入后果:a c b d

能够判断,await前面的表达式值如果是一般值,毋庸期待 then 时长。那么,为什么 b 会在 c 前面输入呢?

await 表达式有执行后果后,await下一行到函数完结局部代码 codex 能够看做搁置到微工作队列中,等同于 Promise.resolve(await xxx).then(()=>codex),这里是伪代码,await 在工夫程序上等效于Promise.prototype.then

  1. await 'foo'执行实现后,console.log('a')被增加到微工作队列;
  2. 持续往下执行同步工作 Promise.resolve(),遇到thenconsole.log(c)增加到微工作队列,以后同步工作执行实现;
  3. 而后执行微工作队列中工作,取出并执行 console.log('a') 输入a
  4. 此时 foo 函数执行实现,遇到 thenconsole.log('b')入队;
  5. 继续执行微工作队列中 console.log('c') 输入 c,此时遇到thenconsole.log('d')入队;
  6. 最初顺次执行取出残余微工作,执行并输入 bd,至此微工作队列清空,代码执行完结;

    await 值为 thenable

    async function foo() {
      await {then(resolve) {resolve()
       console.log('then')
     }
      }
      console.log('a')
    }
    foo().then(() => console.log('b'))
    Promise.resolve()
      .then(() => console.log('c'))
      .then(() => console.log('d'))
      .then(() => console.log('e'))
    // 输入后果 then c a d b e

    await前面表达式值如果是 thenable,须要期待 1 个then 时长,才会去执行后续代码。

  7. foo()执行 await 是一个 thenablePromise 状态变更,执行同步代码 console.log('then'),输入then,此时期待 1 个then 时长;
  8. 持续往下执行同步工作 Promise.resolve(),遇到thenconsole.log('c')退出到微工作队列,以后同步工作执行实现;
  9. 开始执行微工作队列,取出并执行console.log('c'),输入c,微工作队列清空;
  10. 此时步骤 1 期待时长到期,将 await 后续代码 console.log('a') 入队;
  11. 持续步骤 3,遇到 thenconsole.log('d')入队,而后顺次取出 console.log('a')console.log('d')并执行,输入 ad
  12. 执行完 console.log('d') 遇到 thenconsole.log('e')放入队列,取出执行,输入e;

的确有点绕,咱们将 1 个 then 期待时长看做是下一个微工作从入队到执行实现出队的工夫就好。比方这里 c 工作执行实现,下一个工作 d 正筹备进入被 a 插了队。

await 值为 Promise

async function foo() {await Promise.resolve('foo')
  console.log('a')
}
foo().then(() => console.log('b'))
Promise.resolve()
  .then(() => console.log('c'))
  .then(() => console.log('d'))
  .then(() => console.log('e'))
// 输入后果 a c b d e

await前面表达式如果是 Promise,和一般值的后果是一样,毋庸期待then 时长。

为什么不和 returnPromise的情景一样是 2 次呢?原来这是 nodejs 在前期版本优化后的后果:移除了 2 个微工作,1 个throwaway promise,具体起因能够查看「译」更快的 async 函数和 promises。

对于晚期版本(node 11及以前),输入的后果是 c d a e b,须要期待 2 个then 期待时长。

  1. foo()执行 await 是一个 PromisePromise 状态变更,此时期待 2 个 then 时长;
  2. 持续往下执行同步工作 Promise.resolve(),遇到thenconsole.log('c')退出到微工作队列,以后同步工作执行实现;
  3. 开始执行微工作队列,取出并执行console.log('c'),输入c,微工作队列清空;
  4. 遇到 thenconsole.log('d')入队,去除并执行,输入d,微工作队列清空;
  5. 此时步骤 1 期待时长到期,将 await 后续代码 console.log('a') 入队;
  6. 持续步骤 4,遇到 thenconsole.log('e')入队,而后顺次取出 console.log('a')console.log('e')并执行,输入 ae
  7. 执行完 console.log('a') 遇到 thenconsole.log('b')放入队列,取出执行,输入b;

综合 await 表达式值的后果,咱们能够总结

综合 async await

以上咱们仅仅从 asyncreturn值和 await 表达式值繁多视角来看,上面综合他们两个来剖析(对立在 node 12+ 环境)。

await 一个一般函数

首先,await 是一个一般函数(非 async 函数)

function baz() {// console.log('baz')
  // return 'baz'

  // return {//   then(resolve) {//     console.log('baz')
  //     resolve()
  //   }
  // }

  return new Promise((resolve) => {console.log('baz')
    resolve()})
}

async function foo() {await baz()
  console.log('a')
}
foo().then(() => console.log('b'))
Promise.resolve()
  .then(() => console.log('c'))
  .then(() => console.log('d'))
  .then(() => console.log('e'))
// await baz 函数 return 是一般值 输入后果是 baz a c b d e
// await baz 函数 return 是 thenable 输入后果是 baz c a d b e
// await baz 函数 return 是 Promise 输入后果 baz a c b d e

与间接 await 表达式值输入统一。

  • baz 函数 return 是一般值,不期待 then 时长;
  • baz 函数 returnthenable,期待 1 个 then 时长;
  • baz 函数 returnPromise不期待 then 时长;

    await 一个 async 函数

    而后将 baz 函数改成async

    async function baz() {// console.log('baz')
    // return 'baz'
    
    // return {//   then(resolve) {//     console.log('baz')
    //     resolve()
    //   }
    // }
    
    return new Promise((resolve) => {console.log('baz')
      resolve()})
    }
    
    async function foo() {await baz()
    console.log('a')
    }
    foo().then(() => console.log('b'))
    Promise.resolve()
    .then(() => console.log('c'))
    .then(() => console.log('d'))
    .then(() => console.log('e'))
    // await baz 函数 return 是一般值 输入后果是 baz a c b d e
    // await baz 函数 return 是 thenable 输入后果是 baz c a d b e
    // await baz 函数 return 是 Promise 输入后果 baz c d a e b
    // node12 以下版本 await baz 函数 return 是 Promise 输入后果 baz c d e a b

    从中咱们能够发现:await`async函数的期待时长与 async baz 函数的return` 值期待时长保持一致。

  • async baz 函数 return 是一般值,不期待 then 时长;
  • async baz 函数 returnthenable,期待 1 个 then 时长;
  • async baz 函数 returnPromise期待 2 个 then 时长,然而在 node12 以下版本会期待 3 个 then 时长;

综合 async、await、Promise、then 和 setTimeout

上面咱们综合 asyncawaitPromisethensetTimeout来看一道题目

const async1 = async () => {console.log('async1')
  setTimeout(() => {console.log('timer1')
  }, 2000)
  await new Promise((resolve) => {console.log('promise1')
    resolve()})
  console.log('async1 end')
  return Promise.resolve('async1 success')
}
console.log('script start')
async1().then((res) => console.log(res))
console.log('script end')
Promise.resolve(1)
  .then(Promise.resolve(2))
  .catch(3)
  .then((res) => console.log(res))
setTimeout(() => {console.log('timer2')
}, 1000)

思考几分钟,输入后果

// script start
// async1
// promise1
// script end
// async1 end
// 1
// async1 success
// timer2
// timer1
  1. 执行同步工作输入 script startasync1,遇到 setTimeout 放入宏工作队列;
  2. 持续往下执行 await 表达式,执行 new Promise 输入 promise1Promise 状态变更,不期待 then 时长,将后续代码增加到微工作队列;
  3. 持续往下执行输入 script end,执行Promise.resolve(1) 遇到 thenPromise.resolve(2)放入微工作队列;
  4. 再往下执行遇到 setTimeout 放入宏工作队列,至此同步工作执行结束;
  5. 开始执行微工作队列,取出并执行步骤 2 的后续代码输入 async1 end,返回一个已变更的Promise 对象,须要期待 2 个 then 时长;
  6. 持续取出微工作 Promise.resolve(2) 并执行,状态为 resolved 前面走then;
  7. 遇到 then(res) => console.log(res)放入微工作队列,而后取出并执行输入 1,留神:**then** 中是非函数表达式会执行,默认返回的是上一个 **Promise** 的值 then(Promise.resolve(2)) 会透传上一层的1
  8. 此时步骤 5 期待时长到期,将 (res) => console.log(res) 放入微工作队列,而后取出并执行输入async1 success
  9. 最初 2 个定时器别离到期,输入 timer2timer1

如果对这个案例再稍作革新

const async1 = async () => {console.log('async1')
  setTimeout(() => {console.log('timer1')
  }, 2000)
  await new Promise((resolve) => {console.log('promise1')
  })
  console.log('async1 end')
  return 'async1 success'
}
console.log('script start')
async1().then((res) => console.log(res))
console.log('script end')
Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .catch(4)
  .then((res) => console.log(res))
setTimeout(() => {console.log('timer2')
}, 1000)
// 输入后果:// script start
// async1
// promise1
// script end
// 1
// timer2
// timer1

具体过程就不一一列举了,从输入后果能够发现:如果 **await** 表达式的 **Promise** 的状态没有变更,以下代码以及前面的 **then** 永远都不会执行 then 的执行机会是在后面函数执行实现并且 Promise 状态变更当前才会被增加到微工作队列中期待执行。

总结

通过以上就是根本 async`await 的应用场景,以及综合 thenPromisesetTimeout` 的混合应用,大抵能够总结如下几条法则:

  • async函数的 return 值为 thenable 会期待 1 个 then 时长,值为 Promise 会期待 2 个时长;
  • await表达式值为 thenable 会期待 1 个 then 时长,值为 Promisenode12+不期待 then 时长,低版本 node 期待 2 个 then 时长;
  • await一个 async 函数,async函数的 return 值为 thenable 会期待 1 个 then 时长,值为 Promisenode12+会期待 2 个 then 时长,在低版本 node 期待 3 个 then 时长;
  • 如果 then 中是非函数,表达式自身会执行,默认返回的是上一个 Promise 的值,也就是透传上一个 Promise 后果;
  • 如果 await 表达式的 Promise 的状态没有变更,以下代码以及前面的 then 永远都不会执行;

以上案例均通过试验运行得出,流程如有解释谬误,欢送斧正,完~

退出移动版