async & await

asyncawait


# 语法糖

Generator 函数的语法糖。有更好的语义、更好的适用性、返回值是 Promise。

# 例子 1

let a = 0
let b = async () => {
  a = a + (await 10)
  console.log('2', a) // -> '2' 10
  a = (await 10) + a
  console.log('3', a) // -> '3' 20
}
b()
a++
console.log('1', a) // -> '1' 1
1
2
3
4
5
6
7
8
9
10
  • 首先函数 b 先执行,在执行到 await 语句的时候,函数 b 会先暂停执行,并保留当前堆栈中 a 的值 0
  • 因为 await 是一个异步操作,会立即返回一个 pending 状态的 promise,并暂时返回执行代码的控制权,所以函数 b 会被暂停执行,
  • 此时函数外的代码得以继续执行,所以会执行 a++ 操作,然后输出'1' 1,此时 a 的值是 1
  • 此时同步代码执行完成,开始执行异步代码,代码从刚才暂停的地方恢复,将保留的当时执行堆栈里的变量取出,所以此时 a 还是 0
  • await 返回的 promise 有了结果 10,所以 a = a + 10,继续执行输出 '2' 10
  • 然后继续 a = a + 10,继续执行输出 '3' 20
  • 程序结束

如果改成 await b()呢?顶层 await (opens new window)

'2' 10
'3' 20
'1' 21
1
2
3

await 只会阻塞同级代码的执行

# 例子 2

async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2')
}
console.log('script start')
setTimeout(function () {
  console.log('setTimeout')
}, 0)
async1()
new Promise(function (resolve) {
  console.log('promise1')
  resolve()
}).then(function () {
  console.log('promise2')
})
console.log('script end')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

执行结果如下:

'script start'
'async1 start'
'async2'
'promise1'
'script end'
'async1 end'
'promise2'
'setTimeout'
1
2
3
4
5
6
7
8

# 分析

  • 首先输出'script start'
  • 遇到 setTimeout,放入到 Task 队列,主进程继续执行其他代码
  • 紧接着 async1 函数执行,输出'async1 start'
  • 然后 async2 函数被调用,输出 'async2',async2 执行完毕,并返回一个 pendingpromise
  • 由于 await 关键字的存在,返回的 promise 的结果回调被加入到微任务队列中去,且 async1 暂停执行,移交控制权
  • 传给 Promise 构造器的函数立即执行,输出 'promise1',然后立即 resolve,then 方法中的回调被加入到微任务队列
  • 输出 'script end',主线程上的任务执行完毕。开始检查微任务队列,此时微任务队列中有两个任务,然后全部取出并执行它们的回调
  • 依次输出 'async1 end' 'promise2'
  • 微任务队列被清空,检查 Task 队列,存在已经到期定时器任务,取出并执行,输出'setTimeout'

# 原理

async await 的原理其实就是一个自动执行的 generator 语法糖,跟之前说的 thunk 函数的原理一样

function thunk(gen) {
  let g = gen()
  function next(data) {
    let { value, done } = g.next(data)
    if (done === true) return data
    value.then((res) => next(res))
  }
  next()
}

thunk(gen)
1
2
3
4
5
6
7
8
9
10
11

# 总结

  • async 关键字只是用来标记函数是一个异步函数,内部如果没有 await 关键字,那函数内部将从上倒下依次执行完
  • await 关键字相当于 yield,遇到 await 首先会对它后面的表达式或者函数进行求值,并返回一个 pending 的 promise,将回调放入微任务队列,函数暂停执行,保留当前上下文的堆栈信息,并移交控制权
  • 待主线程其他任务执行完毕之后,从 await 处恢复并继续执行
Last Updated: 10/21/2024, 4:15:17 PM