【源码共读】第4期 | co 源码

1. 前言

2. 基础知识

co 是用于将 Generator 函数转换为 Promise 并执行,让其能够像 async、await函数一样自动执行。

2.1 生成器

生成器传递参数

2.2 生成器使用案例

参考若川大佬的文章中的co源码解析,模仿写一个。

  1. 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方法才会得到结果,不执行不会得到结果。那么能不能写一个自动执行的工具完成这个功能。

  1. 第一版-单个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。

  1. 第二版-考虑多个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。那么能不能自动手动,知道结束

  1. 第三版-无限制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 函数方法

  1. slice:用于创建一个对 Array.prototype.slice 的引用。
  2. co:主要函数,将 Generator 函数或 Generator 对象转换为 Promise 并执行。
  3. co.wrap:将给定的 Generator 函数包装成一个返回 Promise 的函数。
  4. toPromise:将 yield 值转换为 Promise。
  5. thunkToPromise:将 thunk(接受回调的函数)转换为 Promise。
  6. arrayToPromise:将包含多个 "yieldables" 的数组转换为 Promise。
  7. objectToPromise:将包含多个 "yieldables" 的对象转换为 Promise。
  8. isPromise:检查一个对象是否为 Promise。
  9. isGenerator:检查一个对象是否为 Generator(具有 nextthrow 方法)。
  10. isGeneratorFunction:检查一个函数是否为 Generator 函数。
  11. 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 源码,学到了:

  1. 如何使用生成器函数和 yield 关键字来编写异步代码。
  2. 如何使用生成器函数的 next()throw() 方法来控制生成器的执行流程。
  3. 如何处理异步操作的返回值。co 函数会将异步操作的返回值作为参数传递给生成器函数的 yield 表达式,并将生成器函数的执行流程暂停,直到下一次调用 next() 方法。

一起学习,共同成长。O^O


ES6 系列之 Generator 的自动执行

学习 koa 源码的整体架构,浅析koa洋葱模型原理和co原理

相关推荐
xiao-xiang14 分钟前
jenkins-通过api获取所有job及最新build信息
前端·servlet·jenkins
C语言魔术师31 分钟前
【小游戏篇】三子棋游戏
前端·算法·游戏
匹马夕阳2 小时前
Vue 3中导航守卫(Navigation Guard)结合Axios实现token认证机制
前端·javascript·vue.js
你熬夜了吗?2 小时前
日历热力图,月度数据可视化图表(日活跃图、格子图)vue组件
前端·vue.js·信息可视化
桂月二二8 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
hunter2062069 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb9 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角9 小时前
CSS 颜色
前端·css
九酒10 小时前
从UI稿到代码优化,看Trae AI 编辑器如何帮助开发者提效
前端·trae
浪浪山小白兔10 小时前
HTML5 新表单属性详解
前端·html·html5