2022-10-7 About 7 min

本章将详细介绍 Babel 对 async / generator 的转译。

# 12.1 前置知识

# Generator 对象

Generator 指的是生成器对象,该对象由 generator 函数返回,Generator 对象符合迭代协议。

function* gen() { // generator 函数
    yield 'a';
    yield 'b';
}

let g = gen(); // "Generaotr 对象 {}"

console.log(g.next()); // { value: "a", done: false }
console.log(g.next()); // { value: "b", done: false }
console.log(g.next()); // { value: undefined, done: true }
1
2
3
4
5
6
7
8
9
10

# 迭代协议

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols#iterable

作为 ECMAScript 2015 的一组补充规范,迭代协议并不是新的内置实现或语法,而是协议。这些协议可以被任何遵循某些约定的对象来实现。

迭代协议具体分为两个协议: 可迭代协议和迭代器协议。

# 可迭代协议

可迭代协议允许 JavaScript 对象设置自身的迭代行为。比如在 for...of 结构中,对象的哪些值可以被遍历到。

要成为可迭代对象,一个对象必须实现 @@iterator 方法。这意味着对象(或者其原型链上的某个对象)必须有一个键为 @@iterator 的属性,可通过常量 Symbol.iterator 访问该属性,示例:

const obj = {
    [Symbol.iterator]: function() { return this }
}
1
2
3

# 迭代器协议

# Generator 对象到底是一个迭代器,还是一个可迭代对象?

回顾下,迭代器指的是实现了 [Symbole.iterator] 属性的对象,可迭代对象是实现了 next() 方法且其返回值是符合规范的 { done, value } 类型的对象。

生成器对象既是迭代器,也是可迭代对象:

let aGeneratorObject = function* (){
    yield 1;
    yield 2;
    yield 3;
}();

typeof aGeneratorObject.next;
// 返回 "function", 因为有一个 next 方法,所以这是一个迭代器

typeof aGeneratorObject[Symbol.iterator];
// 返回 "function", 因为有一个 @@iterator 方法,所以这是一个可迭代对象

aGeneratorObject[Symbol.iterator]() === aGeneratorObject;
// 返回 true, 因为 @@iterator 方法返回自身(即迭代器),所以这是一个格式良好的可迭代对象

[...aGeneratorObject];
// 返回 [1, 2, 3]

console.log(Symbol.iterator in aGeneratorObject)
// 返回 true, 因为 @@iterator 方法是 aGeneratorObject 的一个属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 12.2 Babel 对 generator 函数的转译

# 转译过程概述

并不是所有运行环境都支持 generator 语法的,对于这些环境,Babel 可以将其转译为 ES5 版本,其中,babel-preset-env 和 babel-plugin-transform-runtime 均与 generator 函数转译相关。

例如,待转译的源码为:

function* gen() {
    yield 'a';
    yield 'b';
}
1
2
3
4

在不同的 Babel 配置下,有不同的转译结果。

# regenerator-runtime

资料: https://segmentfault.com/a/1190000023701257

对于源码:

function* gen() {
    yield 'a';
    yield 'b';
}
1
2
3
4

在只配置 babel-preset-env 时,转译结果是:

"use strict";

var _marked = /*#__PURE__*/regeneratorRuntime.mark(gen);

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

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

                case 4:
                case "end":
                return _context.stop();
            }
        }
    }, _marked);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

单独看转译结果,有 2 个问题:

首先,需确认下,转译后代码中的 regeneratorRuntime 来自 babel-runtime 包,babel-runtime 包又依赖了 regenerator-runtime,即依赖关系是:

babel-preset-env -> babel-runtime -> regenerator-runtime
1

regenerator-runtime 工具包是 Facebook 公司推出的用 ES5 写法运行 ES6 的 generator 函数的辅助工具,其目录主要结构为:

regenerator
├─ README.md
├─ bin
├─ main.js
├─ packages
│    ├─ preset
│    ├─ runtime
│    └─ transform
1
2
3
4
5
6
7
8

regeneratorRuntime 定义在文件 regenerator/packages/runtime/runtime.js,具体的,regeneratorRuntime 有如下功能:

源码有 750 行左右,通读该源码可以了解 regeneratorRuntime 的作用。

笔者整理了一份精简版的 regeneratorRuntime 定义,可简要地介绍其核心逻辑。

(function () {
    var window = self = global;
    var ContinueSentinel = {};

    var mark = function (genFun) {
        var generator = Object.create({
            next: function (arg) {
                return this._invoke("next", arg);
            }
        });
        genFun.prototype = generator;
        return genFun;
    };

    function wrap(innerFn, outerFn, self) {
        var generator = Object.create(outerFn.prototype);

        var context = {
            done: false,
            method: "next",
            next: 0,
            prev: 0,
            abrupt: function (type, arg) {
                var record = {};
                record.type = type;
                record.arg = arg;

                return this.complete(record);
            },
            complete: function (record, afterLoc) {
                if (record.type === "return") {
                    this.rval = this.arg = record.arg;
                    this.method = "return";
                    this.next = "end";
                }

                return ContinueSentinel;
            },
            stop: function () {
                this.done = true;
                return this.rval;
            }
        };

        generator._invoke = makeInvokeMethod(innerFn, context);

        return generator;
    }

    function makeInvokeMethod(innerFn, context) {
        var state = "start";

        return function invoke(method, arg) {
            if (state === "completed") {
                return { value: undefined, done: true };
            }

            context.method = method;
            context.arg = arg;

            while (true) {
                state = "executing";

                var record = {
                    type: "normal",
                    arg: innerFn.call(self, context)
                };

                if (record.type === "normal") {
                    state = context.done ? "completed" : "yield";

                    if (record.arg === ContinueSentinel) {
                        continue;
                    }

                    return {
                        value: record.arg,
                        done: context.done
                    };
                }
            }
        };
    }

    window.regeneratorRuntime = {};

    regeneratorRuntime.wrap = wrap;
    regeneratorRuntime.mark = mark;
})();

var _marked = regeneratorRuntime.mark(helloWorldGenerator);

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

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

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

// 运行 gen
var g = gen();
g.next();
g.next();
g.next();
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121

其运行图如下:

Runtime/regenerator-runtime