ZHUSCAFE

关于

阅读 co 源码

预计 14 分钟

阅读了一下 co 的源码,发现其实做法跟我之前看的《你不知道的 JavaScript》中的 Generator + Promise 一节类似。正好巩固了一下这方面的知识,顺便做一些记录。

co 的源码不多,和注释加起来也才两百多行。其中,一个核心函数就是 co 了, co 可以将 Generator 进行自动的执行,本文就来讲一讲 co 是怎么实现自动运行 Generator 的。

co 函数会返回一个 Promise 对象。

function co(gen) {

var ctx = this;

var args = slice.call(arguments, 1);

return new Promise(function(resolve, reject) {
// ...
});
}

在传给这个返回的 Promise 对象的函数体中,做了很多事情。 首先,函数检查传入的 gen 参数是否是一个函数,如果是的话先执行这个函数,然后将 gen 重新赋值(如果这个函数是一个 Generator 的话,此时 gen 变为一个 iterator)。接着,检查 gen 是否是一个有 next 方法的对象,不是的话直接 resolve 返回。

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);

if (!gen || typeof gen.next !== 'function') return resolve(gen);

//...
});
}

之后函数会调用 onFulfilled 方法。 onFulfilled 调用 gennext 方法,并捕获可能发生的错误并进行 reject。如果没有发生错误,则调用 next 方法。

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);

if (!gen || typeof gen.next !== 'function') return resolve(gen);

onFulfilled();


function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);

return null;
}

// ...
});
}

next 方法就是让 Generator 自动完成的核心方法了。 它首先检查迭代器迭代是否完成,如果完成就直接 resolve。否则,将 ret.value 转化成一个 Promise(如果可以的话)。接着检查 value 是否成功转化为一个 Promise,如果是的话,就调用 valuethen 方法,并把 onFulfilledonRejected 作为这个 Promise 完成或被拒绝时候执行的函数。如果没有成功被转换为 Promise,则执行 reject

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);

if (!gen || typeof gen.next !== 'function') return resolve(gen);

onFulfilled();


function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);

return null;
}

// ...

function next(ret) {
if (ret.done) return resolve(ret.value);
var value = toPromise.call(ctx, ret.value);
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
}
});
}

最后再看一下 onRejected,首先,它让 gen 调用 throw 方法,并传入错误参数,如果 Generator 中写了捕捉相应错误的代码,则会捕捉到错误并返回 ret,接着继续执行 ret 就好了(期间也可能抛出新的错误)。不然,错误被重新抛出(没有被 Generator 处理),然后调用 reject

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);

if (!gen || typeof gen.next !== 'function') return resolve(gen);

onFulfilled();


function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);

return null;
}


function onRejected(err) {
var ret;
try {
ret = gen.throw(err);
} catch (e) {
return reject(e);
}
next(ret);
}

function next(ret) {
if (ret.done) return resolve(ret.value);
var value = toPromise.call(ctx, ret.value);
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
}
});
}

这就是 co 的核心函数了,其他的一些函数都是一些判断类型(是否为 Promise等),转换函数(将 thunk 转换为 Promise,将 Array 转换为 Promise 等)和一个 wrap 函数(将 Generator 转换一个返回 Promise 的函数)。

#阅读源码
#co
#异步编程
#JavaScript
#前端