async/await 关键字是 ES7 的新特性,是 Generator 的语法糖,通过这种方法我们可以使用同步的方式来编写异步代码

通过 babel 将

1
2
3
4
async function test() {
await console.log(1)
await console.log(2)
}

转为 ES5 语法,结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
function (module, exports, __webpack_require__) {

"use strict";

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg)
var value = info.value
} catch (error) {
reject(error) return
}
if (info.done) {
resolve(value)
} else {
Promise.resolve(value).then(_next, _throw)
}
}

function _asyncToGenerator(fn) {
return function () {
var self = this, args = arguments
return new Promise(function (resolve, reject) {
var gen = fn.apply(self, args)
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value)
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err)
}
_next(undefined)
})
}
}

function test() {
return _test.apply(this, arguments)
}

function _test() {
_test = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2
return console.log(1)

case 2:
_context.next = 4
return console.log(2)

case 4:
case "end":
return _context.stop()
}
}
}, _callee)
}))
return _test.apply(this, arguments)
}
})

可以看到,将 async/await 代码转换为 ES5 代码需要两步,先将其转换为 Generator 的形式,在使用 ES5 实现 Generator

async/await转换为Generator

可以这样模拟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function myAsync(generateFn) {
return new Promise((resolve, reject) => {
const gen = generateFn()
function step(nextFn) {
let next
try {
next = nextFn()
} catch (err) {
return reject(err)
}
const { value, done } = next
if (done) {
return resolve(value)
} else {
Promise.resolve(value).then(() => {
step(() => gen.next())
}, err => {
step(() => gen.throw(err))
})
}
}
step(() => gen.next())
})
}

ES5实现Generator

典型的生成器如下

1
2
3
4
5
6
7
8
9
function* fn() {
yield 1
yield 2
}

const gene = fn()
console.log(gene.next()) // => { value: 1, done: false }
console.log(gene.next()) // => { value: 2, done: false }
console.log(gene.next()) // => { value: undefined, done: true }

使用 babel 转换后核心部分如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var _marked = /*#__PURE__*/regeneratorRuntime.mark(fn);

function fn() {
return regeneratorRuntime.wrap(function fn$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 1;

case 2:
_context.next = 4;
return 2;

case 4:
case "end":
return _context.stop();
}
}
}, _marked);
}

可以看到,核心的函数部分被编译成了一个包含若干个 case 的 switch 代码块,整个代码块被regeneratorRuntime.wrap包裹住,regenerator 是 facebook 开源的用于将 Generator 语法转换为 ES5 的工具,通过这个工具可以和编译后的代码块模拟 generator

根据编译后的 switch 代码块,我们可以写一个简单的函数来模拟 regenerator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function generator(cb) {
return (function () {
var object = {
next: 0,
stop: function () {}
}

return {
next: function () {
var ret = cb(object);
if (ret === undefined) return { value: undefined, done: true }
return {
value: ret,
done: false
}
}
}
})()
}

简单测试可以看到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function test() {
return generator(function (_context) {
while (1) {
switch ((_context.prev = _context.next)) {
case 0:
_context.next = 2
return 1
case 2:
_context.next = 4
return 2
case 4:
case "end":
return _context.stop()
}
}
})
}

const gene = test()
console.log(gene.next()) // => { value: 1, done: false }
console.log(gene.next()) // => { value: 2, done: false }
console.log(gene.next()) // => { value: undefined, done: true }