什么是co?
co
是用于 Generator
函数的自动执行的一个库,可以理解为一个语法糖,是程序员TJ Holowaychuk 于2013年6月发布的一个小工具。源代码地址
Generator
函数是 ES6
提供的一种异步编程解决方案,可以用来控制函数的执行。通过 yield
关键字控制函数的状态以及 next()
来执行。
co
函数和async await
很像,其实就像是个 Generator
语法糖,不需要我们主动去yield/next
下一步,自动执行。
主函数
co
函数入参为Generator
函数,返回结果为Promise
function co(gen) {
// 获取上下文this
var ctx = this;
// 获取传入函数后所有参数
var args = slice.call(arguments, 1);
// 返回 promise
return new Promise(function(resolve, reject) {
// 如果gen是函数,先执行generator函数
if (typeof gen === 'function') gen = gen.apply(ctx, args);
// 如果gen有值 或 gen非迭代器对象 直接resolve
if (!gen || typeof gen.next !== 'function') return resolve(gen);
/**
* 到这里的时候 gen结构为
* '{ value: xxx, next: function() {},done: true or false}'
*/
// 这里 先去走第一个yield,因为还没走到gen.next() 所以不需要传参数
onFulfilled();
function onFulfilled(res) {
var ret;
try {
// 调用gen.next传参数res,得到yield的结果
ret = gen.next(res);
} catch (e) {
// 出错直接返回
return reject(e);
}
next(ret); // 接受gen.next返回结果 去走判断逻辑
return null;
}
// gen 执行throw方法,出错直接 reject
function onRejected(err) {
var ret;
try {
ret = gen.throw(err);
} catch (e) {
return reject(e);
}
next(ret);
}
// 这里return 只是为了结束函数执行 return结果没有实际意义、切勿混淆
function next(ret) {
// 如果走完了,此时generator执行完毕 返回 resolve
if (ret.done) return resolve(ret.value);
// done为false,说明没走完。 把ret.value转成promise
var value = toPromise.call(ctx, ret.value);
// 如果转换完结果为promise,那么继续链式onFulfilled, onRejected 操作
// 这里就形成了next递归
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
// 如果转换的结果不是promise 那么抛出错误
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
}
});
}
辅助函数
toPromise
function toPromise(obj) {
// 假值 直接返回
if (!obj) return obj;
// 为promise 直接返回
if (isPromise(obj)) return obj;
// 是generator生成器函数 或 generator迭代器对象 调用co返回
if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
// 为函数 转换为promise返回
if ('function' == typeof obj) return thunkToPromise.call(this, obj);
// 为数组转化promise返回
if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
// 为对象转化promise返回
if (isObject(obj)) return objectToPromise.call(this, obj);
// 返回
return obj;
}
thunkPromise
用thunk
方式去将函数转化为promise
。 Thunk函数的含义和用法
function thunkToPromise(fn) {
var ctx = this;
return new Promise(function (resolve, reject) {
fn.call(ctx, function (err, res) {
if (err) return reject(err);
if (arguments.length > 2) res = slice.call(arguments, 1);
resolve(res);
});
});
}
arrayToPromise
将数组的每一项转化为promise
调用Promise.all
返回。
function arrayToPromise(obj) {
return Promise.all(obj.map(toPromise, this));
}
objectToPromise
将对象的每一项转化为promise
调用Promise.all
返回。
function objectToPromise(obj){
// 初始化 result
var results = new obj.constructor();
// 便利所有键名
var keys = Object.keys(obj);
// promise组成的数组
var promises = [];
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
// 将每一项转化为promise
var promise = toPromise.call(this, obj[key]);
// 如果为promise 则调用defer
if (promise && isPromise(promise)) defer(promise, key);
else results[key] = obj[key];
}
// 最后全部一起返回
return Promise.all(promises).then(function () {
return results;
});
// 将promise resolve结果塞到数组里
function defer(promise, key) {
// 先赋值为undefined
results[key] = undefined;
// 塞到promises数组里,并且把promise resolve的结果存到result里
promises.push(promise.then(function (res) {
results[key] = res;
}));
}
}
isPromise、isGenerator、isGeneratorFunction、isObject
// 判断是否为promise
function isPromise(obj) {
return 'function' == typeof obj.then;
}
// 判断是否为generator迭代器对象
function isGenerator(obj) {
return 'function' == typeof obj.next && 'function' == typeof obj.throw;
}
// 判断是否为generator生成器函数
function isGeneratorFunction(obj) {
var constructor = obj.constructor;
if (!constructor) return false;
if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true;
return isGenerator(constructor.prototype);
}
// 判断是否为对象
function isObject(val) {
return Object == val.constructor;
}
co.wrap 函数
将 Generator
函数转换成 promise
函数。可重复使用,类似于缓存函数功能。
借助于高阶函数的特性,返回一个新函数createPromise
,然后传给它的参数都会被导入到Generator
函数中。
co.wrap = function (fn) {
createPromise.__generatorFunction__ = fn;
return createPromise;
function createPromise() {
return co.call(this, fn.apply(this, arguments));
}
};
// 例子🌰
// 查中文名1
co(function* () {
const chineseName = yield searchChineseName('tom')
return chineseName
})
// 查中文名2
co(function* () {
const chineseName = yield searchChineseName('jarry')
return chineseName
})
// 无法复用,通过co.wrap实现重复利用
const getChineseName = co.wrap(function* (name) {
const filename = yield searchChineseName(name)
return filename
})
getChineseName('tom').then(res => {})
getChineseName('jarry').then(res => {})
co函数导出方式
module.exports = co['default'] = co.co = co;
co
函数多种的方式的导出,那么也就有多种引入方式了。
const co = require('co')
require('co').co
import co from 'co'
import * as co from 'co'
import { co } form 'co'
async await简单实现
由此可见,async await
也不过也是一个generator
语法糖,就是像co
函数一样,如果上面都看懂了的话,下面的代码也不就不难理解了。
function asyncToGenerator(generatorFunc) {
return function() {
const gen = generatorFunc.apply(this, arguments)
return new Promise((resolve, reject) => {
function step(key, arg) {
let generatorResult
try {
generatorResult = gen[key](arg)
} catch (error) {
return reject(error)
}
const { value, done } = generatorResult
if (done) {
return resolve(value)
} else {
return Promise.resolve(value).then(val => step('next', val), err => step('throw', err))
}
}
step("next")
})
}
}
简单总结
通过co的源码的学习,对generaitor
、迭代器
、生成器
、async await
有了更深的理解,之后如果面试再遇到相关手写自执行generator
,应该不成问题了。
写在最后
我是思唯,一个开始尝试向社区输出的东西的前端开发一枚。
文中如有错误,欢迎大家在评论区指正,如果这篇文章帮助到了你,欢迎点赞👍和关注❤️