本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
这是源码共读的第4期,链接: juejin.cn/post/708498… 。
在co源码之前,先看几段简单的代码
// 写一个简单的请求
function resuest(ms = 3000) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ name: 'sam' })
}, ms);
});
}
// 1. 获取generator的值
function* generatorFunc() {
const res = yield resquest()
console.log(res, 'generatorFunc-res');
}
generatorFunc(); // 不会自动执行
简单来说co,就是将generator自动执行,再返回一个promise。generator函数它不会自动执行,还要一步步调用next(),也就是调用一步走一步。
所有后来有了async、await函数。
async function asyncFunc() {
const res = await resquest();
console.log(res, 'asyncFunc-res await 函数 自动执行');
}
asyncFunc();
也就是说co需要做的事,是让generator像async、await函数一样自动执行。
模拟实现简版co(第一版)
根据generator的特性,其实很容易实现以下代码:
// 获取generator的值
function* generatorFunc() {
const res = yield request()
console.log(res, 'generatorFunc-res')
}
function onSimple(gen) {
gen = gen()
console.log(gen, 'gen')
const ret = gen.next()
const promise = ret.value;
promise.then(res => {
gen.next();
})
}
coSimple(generatorFunc);
// { name: 'sam' }"generatorFunc-res"
模拟实现简版co(第二版)
但是实际上,不会上面那么简单的。有可能是多个yield和传参数的情况。传参可以通过以下两行代码解决。
const args = Array.prototype.slice.call(arguments, 1);
gen = gen.apply(ctx, args)
两个yield,可以重新调用一下promise.then
// 多个yeild,传参情况
function* generatorFunc(suffix = '') {
const res = request()
console.log(res + 'generatorFunc-res');
const res2 = resquest()
console.log(res2 + 'generatorFunc-res');
}
function coSimple(gen) {
const ctx = this;
const args = Array.prototype.slice.call(arguments, 1);
gen.apply(ctx, args);
console.log(gen, 'gen')
const ret = gen.next();
const promise = ret.value;
promise.then(res => {
const ret = gen.next(res)
const promise = ret.value
promise.then(res => {
gen.next(res)
})
})
}
coSimple(generatorFunc, '------后缀');
模拟实现简版co(第三版)
问题是肯定不止两次,无限次的yield的呢,这时肯定要把重复的封装起来。而且返回的是promise,这就实现了如下版本的代码。
function* generator(suffix = '') {
const res = yield request();
console.log(res, 'generator-res' + suffix);
const res1 = yield request();
console.log(res1, 'generator-res2' + suffix);
const res2 = yield request();
console.log(res2, 'generator-res3' + suffix);
const res3 = yield request();
console.log(res3, 'generator-res4' + suffix);
}
function coSimple(gen) {
const ctx = this;
const args = Array.prototype.slice.call(arguments, 1);
gen = gen.apply(ctx, args);
console.log(gen, 'gen');
return new Promise((resolve, reject) => {
onFulfilled();
function onFulfilled(res) {
const ret = gen.next(res)
next(ret);
}
function next(ret) {
const promise = ret.value();
promise && promise.then(onFulfilled);
}
})
}
coSimple(generatorFunc, '--------后缀');
第三版实现的简版co中,还没有考虑报错和一些参数合法的情况。
最终看下co源码
function co(gen) {
const ctx = this;
const args = slice.call(arguments, 1)
return new Promise(function(resolve, reject) {
if(typeof gen === 'function') gen.apply(ctx, args);
if(!gen || typeof gen.next !== 'function') return resolve(gen);
onFulfilled()
function onFulfilled(res) {
let ret
try {
ret = res.next()
} catch(e) {
return reject(res)
}
next(ret)
}
function onRejected(err) {
let ret
try {
ret = gen.throw(err)
} catch(e) {
return reject(e)
}
next(ret)
}
// 反复执行自己
function next(ret) {
// 检查当前是否为Generator函数的最后一步,如果是就返回
if(ret.done) return resolve(ret.value)
// 确保返回值是promise对象
let value = toPromise.call(ctx, ret.value)
// 使用then方法,为返回值加上回到函数,然后通过onFulfilled函数再次调用next函数
if (value && toPromise(value)) return value.then(onFulfilled, onRejected);
// 在参数不符合要求的情况下(参数非Thunk函数和Promise对象),将Promise对象的状态改为rejected,从而终止执行。
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'))
}
})
}
此文章为12月Day2源码共读,以梦为马,抓住2023年的尾巴💪💪