JS 基础之异步(六):co 函数库
sisterAn opened this issue · 0 comments
sisterAn commented
co 函数库是 TJ 大神基于ES6 generator 的异步解决方案。要理解 co ,你必须首先理解 ES6 generator,可以看我之前的文章JS 基础之异步(五):Generator,这里不在赘述。
co 现在已经不怎么使用了,一些老版本的库里可能使用它,我是在看 koa 源码的时候看到它的。
co 最大的好处在于通过它可以把异步的流程以同步的方式书写出来,并且可以使用 try/catch。
废话少说,上实例:
var co = require('co')
var fs = require('fs')
// wrap the function to thunk
function readFile(filename) {
return new Promise(function(resolve, reject) {
fs.readFile(filename, function(err, date) {
if (err) reject(err)
resolve(data)
})
})
}
// generator 函数
function *gen() {
var file1 = yield readFile('./file/1.txt') // 1.txt内容为:content in 1.txt
var file2 = yield readFile('./file/2.txt') // 2.txt内容为:content in 2.txt
console.log(file1)
console.log(file2)
return 'done'
}
// co
co(gen).then(function(err, result) {
console.log(result)
})
// content in 1.txt
// content in 2.txt
// done
在上例中,co 函数库可以让你不用编写 generator 函数的执行器,generator 函数只要放在 co 函数里,就会自动执行。
所以,我们可以模拟 co 的实现:
function myCo(generator) {
var gen = generator()
function next(data) {
var result = gen.next(data)
if (result.done) return result.value
result.value.then(function (data) {
next(data)
})
}
next()
}
myCo(gen)
再看一个例子:
co(function *(){
try {
var res = yield get('http://baidu.com');
console.log(res);
} catch(e) {
console.log(e.code)
}
})
co 函数库其实就是将两种自动执行器(Thunk 函数和 Promise 对象),包装成一个库。但用 co 的一个代价是 Generator 函数的 yield 命令后面必须返回一个 Thunk 或者一个 Promise。
源码实现:
function co(gen) { // co 接受一个 generator 函数
var ctx = this
var args = slice.call(arguments, 1)
return new Promise(function(resolve, reject) { // co 返回一个 Promise 对象
if(typeof gen === 'function') gen = gen.apply(ctx, args) // gen 为 generator 函数,执行该函数
if(!gen || typeof gen.next !== 'function') return resolve(gen) // 不是则返回并更新 Promise状态为 resolve
onFulfilled() // 将generator 函数的 next 方法包装成 onFulfilled,主要是为了能够捕获抛出的异常
/**
* @param {Mixed} res
* @return {Promise}
* @api private
*/
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res)
} catch (err) {
return reject(err)
}
next(ret)
}
/**
* @param {Error} err
* @return {Promise}
* @api private
*/
function onRejected(err) {
var ret
try {
ret = gen.throw(err)
} catch (err) {
return reject(err)
}
next(ret)
}
/**
* Get the next value in the generator,
* return a promise.
*
* @param {Object} ret
* @return {Promise}
* @api private
*/
function next(ret) {
if(ret.done) return resolve(ret.value)
var value = toPromise.call(ctx, ret.value) // if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
if(value && isPromise(value)) return value.then(onFulfilled, onRejected)
return onRejected(new TypeError('You may only yield a function, promise, generator, but the following object was passed: ' + String(ret.value) + '"'))
}
})
}
注意:onFulfilled
这个函数只在两种情况下被调用,一种是调用co的时候执行,还有一种是当前promise中的所有逻辑都执行完毕后执行