Javascript async/await 原理 & 实现

ES7 在语法层面上支持了 async/await 关键字,我们将在代码层面上模拟实现 async/await 来理解它的工作原理。

本文旨在说明其工作原理,你应该具备以下前置知识:

  • 熟悉 Promise 的概念及使用
  • 熟悉 async/await 的概念及使用
  • 了解 Generator 的概念

Generator 基础复习

首先复习一下 Js Generator 的基本用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function * work() {
const r = yield 'hi'
console.log(r) // 'ha'
return
}

const gen = work()
console.log(gen) // Object [Generator] {}

let result = gen.next()
console.log(result) // { value: 'hi', done: false }

result = gen.next('ha')
console.log(result) // { value: undefined', done: true }

// output summary:
// Object [Generator] {}
// { value: 'hi', done: false }
// ha
// { value: undefined', done: true }

我们调用 work() 这个函数时它返回的是一个 Generator 对象 gen
然后我们调用 gen.next() 返回值是 { value: 'hi', done: false }, 其中 value 字段就是 yield 关键字返回给我们的数据,done 字段表示该 Generator 函数当前并没有执行结束,并且暂停在了 yield 所在的这一行。

当我们第二次调用 gen.next('ha') 的时候,Generator 函数继续执行,并将我们传给 next() 函数的参数值 'ha' 返回并赋值给变量 r,然后继续运行并打印出 r,直到遇到 return 语句,函数返回 { value: undefined', done: true } 给我们。

其中 value 是返回值, done 表示 Generator 函数已执行结束并返回。

Generator 函数的一个特点是可以使用 yeild 关键字暂停当前函数的执行,并向调用者返回相应数据以及自己的运行状态( 代码中的 done 字段)。
另外一个特点是,调用者通过 next() 函数恢复 Generator 执行时可以通过参数传数据给 Generator 作为 yield 的返回值 (赋给代码中的变量 r)。

run()

有了前面关于 Generator 知识的复习,我们实现本文的核心函数;
它接收一个 Generator 函数 fn 作为参数,它的功能就是模拟 async/await 机制运行该 fn 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function run(fn) {
const gen = fn()
function _next(data){
const { value, done } = gen.next(data)
if(done) return value

if(value.then){
value.then(data => _next(data))
}else{
_next(value)
}
}
_next()
}

在解释我们这个 run() 函数之前,先看一下它的用法示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function sleep(duration) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(`slept ${duration}ms`), duration)
})
}

function * work () {
const ret = yield sleep(1000)
console.log(ret)
const ret2 = yield sleep(2000)
console.log(ret2)
}

run(work)
// output
// slept 1000ms
// slept 2000ms

看了这个示例代码,应该就能明白 run() 的作用了,实际上通过这个函数,我们已经模拟实现了 async/await 机制;

我们做个不严谨的类比来说明:

  • function * work() { } 中间的 * 号就相当于 async 关键字的作用;
  • yield 关键字就相当于 await 关键字;

总结

实际上 async/await 关键字也可以认为是 ES7 的语法糖,我们已经通过 run() 函数来模拟了其工作原理,至于 Js 引擎具体怎么实现的,未来我们写 分析 v8 引擎实现相关文章的时候再细究。