if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj); 所以,其实我们的代码:
// yield generator function var b = yield fn; // yield generator var c = yield fn(5); 实际上会变成类似这样:
var b = yield co(fn); var c = yield co(fn_generator); 而 co 方法最后会返回一个 Promise,同时还会对传入的参数进行处理,如果是函数则直接调用这个 generator function 取得 generator,大致如下:
function co(gen) { var ctx = this; var args = slice.call(arguments, 1)
return new Promise(function(resolve, reject) { if (typeof gen === ‘function’) gen = gen.apply(ctx, args); // 其他代码 }); } 所以,再 yield 这个 Promise 后,保证了我们取到了正常结果。
其实,我们也可以直接用“原生”的语法写成:
var b = yield* fn(); var c = yield* fn(5); 因为 yield* 接受一个 iterable object 作为参数(比如:generator function 返回的 generator object),所以,我们需要调用 generator function 来返回一个 generator object。
用原生语法,抛弃 co 的黑魔法,换取一点点点点性能提升 明确表明自己的意图,避免混淆 调用时保证正确的 this 指向 第一点上面有提到,就不展开说了。
第二点我们来解释下。看看下面的代码,你能知道 yield 后面的到底是个什么鬼吗?
var v = yield later(); 如果你不看 later 的实现,实际上你并不知道。later 方法完全可以实现成返回一个 Promise,返回一个 thunk,返回一个数组、对象,或者就是返回 generator object。
但是下面这样的代码:
var v = yield* later(); 我们就很自信的知道 later 肯定是返回一个 generator object 了。
第三点也是 co 黑魔法的一个大坑。
比如我们有下面这样的代码:
function later(n, t) { t = t || 1000; return function(done) { setTimeout(function() { done(null, n); }, t); }; } function Runner() { this.name = ‘runner’; } Runner.prototype.run = function* (t) { var r = yield later(this.name, t); return ‘run->’ + r; };
var runner = new Runner(); var result = yield runner.run; console.log(result); // -> 输出:run->undefined 你会发现, result 是 run->undefined,这说明 this 的指向不对了。这是因为 co 实际上是类似这样执行上面的代码的:
var runner = new Runner(); var result = yield co(function() { runner.run.call(this); // 这里的this变成了co的实例… }); 破解大法也很简单,就是使用原生语法:
var result = yield* runner.run(); console.log(result); // -> 输出:run->runner 那么,我就说完了。如果各位没有了解过 co 的黑魔法,看的可能会有点累,推荐大家看看参考资料的第一条。