1. 前言
-
本文参加了由 公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
-
这是源码共读的第4期,链接:co 源码
2. 基础知识
co 是用于将 Generator 函数转换为 Promise 并执行,让其能够像 async、await
函数一样自动执行。
2.1 生成器
2.2 生成器使用案例
参考若川大佬的文章中的co源码解析,模仿写一个。
- generator的执行
js
// 模拟请求
function request(ms = 1000) {
return new Promise(resolve => {
setTimeout(() => {
resolve(111)
}, ms)
})
}
// 生成器函数
function* generatorFunc() {
const res = yield request()
console.log('打印***res', res)
}
generatorFunc()
会得到一个迭代器对象
,只有调用next方法才会得到结果,不执行不会得到结果。那么能不能写一个自动执行的工具完成这个功能。
- 第一版-单个yield自动执行
js
function request(ms = 1000) {
return new Promise(resolve => {
setTimeout(() => {
resolve(111)
}, ms)
})
}
function* generatorFunc() {
const res = yield request()
console.log('打印***res', res)
}
function coSimple(gen) {
gen = gen()
console.log('打印***gen', gen)
const ret = gen.next()
const promise = ret.value
promise.then(res => {
gen.next(res)
})
}
coSimple(generatorFunc)
使用next进行调用,将结果包装成promise 执行结果使用gen.next(res)返回,不调用next,则生成器函数yield后的res为undefined。
- 第二版-考虑多个yield和传参
js
function* generatorFunc2(suffix=''){
const res = yield request()
console.log('打印***res-1'+suffix,res)
const res2 = yield request()
console.log('打印***res-2'+suffix,res2)
}
function coSimple2(gen){
const ctx = this
const args = Array.prototype.slice.call(arguments,1)// 转成数组
gen = gen.apply(ctx,args)
console.log('打印***gen',gen)
// 第一个调用
const ret = gen.next()
console.log('打印***ret',ret)
const promise = ret.value
promise.then(res=>{
console.log('打印***res',res)
const ret = gen.next(res)// 此处不传入,则yield后面读取不到res,111
const promise = ret.value
promise.then(res=>{
gen.next(res)
})
})
}
coSimple2(generatorFunc2,'888')
此处多个yield调用使用嵌套方式,每次手动获取value。那么能不能自动手动,知道结束
- 第三版-无限制yield
js
let index = 0
function request(ms = 1000) {
return new Promise(resolve => {
setTimeout(() => {
resolve(index++)
}, ms)
})
}
function* generatorFunc3(suffix=''){
const res = yield request()
console.log('打印***res-1'+suffix,res)
const res2 = yield request()
console.log('打印***res-2'+suffix,res2)
const res3 = yield request()
console.log('打印***res-3'+suffix,res3)
const res4 = yield request()
console.log('打印***res-4'+suffix,res4)
}
function coSimple3(gen){
const ctx = this
const args = Array.prototype.slice.call(arguments,1)
gen = gen.apply(ctx,args)
console.log('打印***gen',gen)
return new Promise(()=>{
function onFulfilled(res){
const ret = gen.next(res)
console.log('打印***ret',ret)
next(ret)
}
function next(ret){
const promise = ret.value
promise&&promise.then(onFulfilled)
}
onFulfilled()
})
}
coSimple3(generatorFunc3)
修改了请求,方便看出每次参数的传递。
返回promise,定义onFulfilled,去手动调用next执行request,使用next获取上一次的结果,并调用自身,直到所有yield执行完毕。
3. 源码解析
3.1 函数方法
slice
:用于创建一个对Array.prototype.slice
的引用。co
:主要函数,将 Generator 函数或 Generator 对象转换为 Promise 并执行。co.wrap
:将给定的 Generator 函数包装成一个返回 Promise 的函数。toPromise
:将yield
值转换为 Promise。thunkToPromise
:将 thunk(接受回调的函数)转换为 Promise。arrayToPromise
:将包含多个 "yieldables" 的数组转换为 Promise。objectToPromise
:将包含多个 "yieldables" 的对象转换为 Promise。isPromise
:检查一个对象是否为 Promise。isGenerator
:检查一个对象是否为 Generator(具有next
和throw
方法)。isGeneratorFunction
:检查一个函数是否为 Generator 函数。isObject
:检查一个值是否为普通对象。
3.2 co函数
js
function co(gen) {
var ctx = this; // 保存当前上下文
var args = slice.call(arguments, 1); // 获取除了第一个参数(gen)之外的其他参数
// 我们将所有内容包装在一个 Promise 中,以避免 Promise 链式调用导致的错误。
return new Promise(function(resolve, reject) {
if (typeof gen === 'function') gen = gen.apply(ctx, args); // 如果 gen 是函数,则执行函数并将结果赋值给 gen
if (!gen || typeof gen.next !== 'function') return resolve(gen); // 如果 gen 不是函数,或者不具有 next 方法,则直接返回结果为 gen 的 Promise
onFulfilled();
// 当前步骤执行成功时的处理函数
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res); // 执行生成器的 next 方法,并将结果赋值给 ret
} catch (e) {
return reject(e); // 如果出现异常则将异常作为 Promise 的拒绝理由
}
next(ret); // 继续执行下一步
return null;
}
// 当前步骤执行失败时的处理函数
function onRejected(err) {
var ret;
try {
ret = gen.throw(err); // 执行生成器的 throw 方法,并将结果赋值给 ret
} catch (e) {
return reject(e); // 如果出现异常则将异常作为 Promise 的拒绝理由
}
next(ret); // 继续执行下一步
}
// 获取生成器的下一个值,并返回一个 Promise
function next(ret) {
if (ret.done) return resolve(ret.value); // 如果生成器完成,则将生成器的结果作为 Promise 的解决值
var value = toPromise.call(ctx, ret.value); // 将生成器的返回值转换为 Promise
if (value && isPromise(value)) return value.then(onFulfilled, onRejected); // 如果返回值是 Promise,则等待 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) + '"')); // 如果生成器的返回值不是函数、Promise、Generator、数组或对象,则拒绝 Promise
}
});
}
在 co
函数中,它会首先判断传入的参数 gen
是否为 Generator 函数,如果是则执行该函数得到 Generator 对象。然后,在一个新的 Promise 中执行 Generator 对象的 next
方法,根据返回的结果进行相应的处理,直到 Generator 完成(done
为 true),最终返回 Promise 的结果。
4. 总结
通过学习 co
源码,学到了:
- 如何使用生成器函数和
yield
关键字来编写异步代码。 - 如何使用生成器函数的
next()
和throw()
方法来控制生成器的执行流程。 - 如何处理异步操作的返回值。
co
函数会将异步操作的返回值作为参数传递给生成器函数的yield
表达式,并将生成器函数的执行流程暂停,直到下一次调用next()
方法。
一起学习,共同成长。O^O