生成器是异步编程工具箱中新增的一种非常重要的工具。但是, 这是 ES6 中新增的语法,这意味着你没法像对待 Promise(这只是一种新的 API)那样使 用生成器。所以如果不能忽略 ES6 前的浏览器的话,怎么才能把生成器引入到我们的浏览器 JavaScript 中呢?

对 ES6 中所有的语法扩展来说,都有工具(最常见的术语是 transpiler,指 trans-compiler, 翻译编译器)用于接收 ES6 语法并将其翻译为等价(但是显然要丑陋一些!)的前 ES6 代 码。因此,生成器可以被翻译为具有同样功能但可以工作于 ES5 及之前的代码。 可怎么实现呢?显然 yield 的“魔法”看起来并不那么容易翻译。实际上,我们之前在讨 论基于闭包的迭代器时已经暗示了一种解决方案。

手工变换

在讨论 transpiler 之前,先来推导一下对生成器来说手工变换是如何实现的。这不只是一个 理论上的练习,因为这个练习实际上可以帮助我们更深入理解其工作原理。 考虑:

// request(..)是一个支持Promise的Ajax工具
     function *foo(url) {
         try {
             console.log( "requesting:", url );
             var val = yield request( url );
             console.log( val );
         }
         catch (err) {
             console.log( "Oops:", err );
             return false;
         }
     }
     var it = foo( "http://some.url.1" );

首先要观察到的是,我们仍然需要一个可以调用的普通函数 foo(),它仍然需要返回一个 迭代器。因此,先把非生成器变换的轮廓刻画出来:

function foo(url) {
         // ..
// 构造并返回一个迭代器 return {
             next: function(v) {
                 // ..
             },
             throw: function(e) {
// .. }
}; }
var it = foo( "http://some.url.1" );

接下来要观察到的是,生成器是通过暂停自己的作用域 / 状态实现它的“魔法”的。可以 通过函数闭包(参见本系列的《你不知道的 JavaScript(上卷)》的“作用域和闭包”部分) 来模拟这一点。为了理解这样的代码是如何编写的,我们先给生成器的各个部分标注上状 态值:

// request(..)是一个支持Promise的Ajax工具 function *foo(url) {
// 状态1
         try {
             console.log( "requesting:", url );
             var TMP1 = request( url );
// 状态2
var val = yield TMP1; console.log( val );
         }
         catch (err) {
// 状态3
console.log( "Oops:", err ); return false;
} }

换句话说,1 是起始状态,2 是 request(..) 成功后的状态,3 是 request(..) 失败的状态。 你大概能够想象出如何把任何额外的 yield 步骤编码为更多的状态。