前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >co 源码精读

co 源码精读

作者头像
逆葵
发布于 2019-04-25 03:13:23
发布于 2019-04-25 03:13:23
98600
代码可运行
举报
文章被收录于专栏:FECodingFECoding
运行总次数:0
代码可运行

co 是著名的 TJ 于 2013 年推出的一个利用 ES6 的 Generator 函数来解决异步操作的开源项目,也是后来 JavaScript 异步操作的终极解决方案—— async/await 的先驱。时至今日,co 版本号已经来到了 4.x,不过其代码仍然只有寥寥数百行,十分适合阅读与学习。下面我们就来看一下 co 是如何对异步操作进行处理的。

首先先来看一下 co 的基本用法。co 使用起来十分方便,只需要将一个 Generator 函数作为参数传给 co(),就能在该函数中像同步代码一样编写异步代码。看一下官方示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var co = require('co');

co(function *(){
  // yield any promise
  var result = yield Promise.resolve(true);
}).catch(onerror);

// focus on this
co(function *(){
  // resolve multiple promises in parallel
  var a = Promise.resolve(1);
  var b = Promise.resolve(2);
  var c = Promise.resolve(3);
  var res = yield [a, b, c];
  console.log(res);
  // => [1, 2, 3]
}).catch(onerror);

// errors can be try/catched
co(function *(){
  try {
    yield Promise.reject(new Error('boom'));
  } catch (err) {
    console.error(err.message); // "boom"
 }
}).catch(onerror);

function onerror(err) {
  // log any uncaught errors
  // co will not throw any errors you do not handle!!!
  // HANDLE ALL YOUR ERRORS!!!
  console.error(err.stack);
}

第 9 行的函数体中,a、b、c 的值都是异步返回的,但是却可以像同步一样调用。这便是 co 的魔力。

除此之外,co 还提供了一个 API—— co.wrap() ,用于将被 co 包裹的 generator 函数转换成为一个返回 promise 的普通函数,示例如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var fn = co.wrap(function* (val) {
  return yield Promise.resolve(val);
});

fn(true).then(function (val) {
});

了解完了 API,我们就可以来看一下 co 内部的实现原理。下面是经过笔者注解(中文注释)的源码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * slice() reference.
 */

var slice = Array.prototype.slice;

/**
 * Expose `co`.
 */

module.exports = co['default'] = co.co = co;

/**
 * Execute the generator function or a generator
 * and return a promise.
 *
 * @param {Function} fn
 * @return {Promise}
 * @api public
 */

// 这里是核心代码
function co(gen) {
  // 缓存上下文
  var ctx = this;
  var args = slice.call(arguments, 1)

  // we wrap everything in a promise to avoid promise chaining,
  // which leads to memory leak errors.
  // see https://github.com/tj/co/issues/180
  
  // co() 返回值为 promise
  return new Promise(function(resolve, reject) {
    // 执行一次 generator 函数以获取遍历器
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    if (!gen || typeof gen.next !== 'function') return resolve(gen);

    // 启动执行
    onFulfilled();

    /**
     * @param {Mixed} res
     * @return {Promise}
     * @api private
     */

    function onFulfilled(res) {
      var ret;
      // 执行 generator 函数的 next 方法,如出错则 reject
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      
      // 继续执行 next 函数
      next(ret);
    }

    /**
     * @param {Error} err
     * @return {Promise}
     * @api private
     */

    function onRejected(err) {
      var ret;
      // 执行 generator 函数的 throw 方法(用于抛出错误,由 generator 函数内部进行错误处理),如出错则 reject
      try {
        ret = gen.throw(err);
      } catch (e) {
        return reject(e);
      }
      
      // 继续执行 next 函数
      next(ret);
    }

    /**
     * Get the next value in the generator,
     * return a promise.
     *
     * @param {Object} ret
     * @return {Promise}
     * @api private
     */
	
    // 关键所在,自动连续执行 next()
    function next(ret) {
      // generator 函数执行完毕,返回
      if (ret.done) return resolve(ret.value);
      // 每次均将 yield 返回的 value 转换为 promise 
      var value = toPromise.call(ctx, ret.value);
      // 使用 Promise.then 递归连续执行
      // onFulfilled 和 onRejected 函数中会继续调用 next 函数自身
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
      // yield 返回的 value 无法转换为 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) + '"'));
    }
  });
}

/**
 * Wrap the given generator `fn` into a
 * function that returns a promise.
 * This is a separate function so that
 * every `co()` call doesn't create a new,
 * unnecessary closure.
 *
 * @param {GeneratorFunction} fn
 * @return {Function}
 * @api public
 */

// 单看代码可能略为有点晦涩,结合上面 co.wrap 的使用示例比较容易理解
// 实际上这里只是作了一层简单的封装,方便传参
co.wrap = function (fn) {
  createPromise.__generatorFunction__ = fn;
  return createPromise;
  function createPromise() {
    // 传入的参数通过 apply 方式作为 fn 的参数执行
    // 然后仍然是调用 co 函数,回到上面的逻辑
    return co.call(this, fn.apply(this, arguments));
  }
};


// 下面是一些功能性的 utils 函数,根据注释便可知晓其用法,此处省略函数具体实现,不作过多赘述

/**
 * Convert a `yield`ed value into a promise.
 */
function toPromise(obj) {}

/**
 * Convert a thunk to a promise.
 */
function thunkToPromise(fn) {}

/**
 * Convert an array of "yieldables" to a promise.
 * Uses `Promise.all()` internally.
 */
function arrayToPromise(obj) {}

/**
 * Convert an object of "yieldables" to a promise.
 * Uses `Promise.all()` internally.
 */
function objectToPromise(obj){}

/**
 * Check if `obj` is a promise.
 */
function isPromise(obj) {}

/**
 * Check if `obj` is a generator.
 */
function isGenerator(obj) {}

/**
 * Check if `obj` is a generator function.
 */
function isGeneratorFunction(obj) {}

/**
 * Check for plain object.
 */
function isObject(val) {}

我们可以看到,co 的核心逻辑在于第 90 行的 next 函数,这里将每一次 yield 的返回值包装成 Promise 对象,在 Promise 的 onFulfilled 和 onRejected 状态中继续递归调用 next 函数,保证链式调用自动执行,使得异步的代码能够以同步的方式运行。

可能还有读者有疑问,如果不借助 Promise,这样的链式自动执行是否还可以实现。事实上,在 co 的 4.0.0 版本以前,其底层实现就没有借助 Promise,而是采用了 Thunk 函数的方式。感兴趣的读者可以切换到 3.1.0 版本学习源码。

注:如有关于什么是 Thunk 函数的疑问,点击这里

阮一峰老师在《ECMAScript 6 入门》一书中对于 Generator 函数自动执行的原理有一个精准的结论:“自动执行的关键是,必须有一种机制,自动控制 Generator 函数的流程,接收和交还程序的执行权。回调函数可以做到这一点,Promise 对象也可以做到这一点。”我们还是以 Promise 对象为例解释一下这句话:将通过 yield 返回的对象的 value 保持为一个 Promise 对象,执行之,即可拿到程序的执行权。然后通过 Promise.then 和 Promise.reject 方法中调用 generator 的 next 方法,可以交还程序执行权。如此达到自动执行 generator 函数的效果。

最后,感谢 co 这样的优秀项目作为开拓者,才有了后来的 async/await ,让 JavaScript 开发人员不再因为这门语言独特的单线程特性而深陷异步编程带来的困扰。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Promise 源码分析
then/promise项目是基于Promises/A+标准实现的Promise库,从这个项目当中,我们来看Promise的原理是什么,它是如何做到的,从而更加熟悉Promise
用户2356368
2019/04/03
7770
学习 koa 源码的整体架构,浅析koa洋葱模型原理和co原理
感兴趣的读者可以点击阅读。 其他源码计划中的有:express、vue-rotuer、redux、 react-redux 等源码,不知何时能写完(哭泣),欢迎持续关注我(若川)。
若川
2020/03/19
1.1K0
koa框架源码解读
虽然经常用koa作为NodeJS Web项目的框架,但一直都是只知道怎么做,但并不知道它究竟是怎么实现的。今天花了些时间来研究它,在这里记录一下。 Generator函数 Generator函数形式 Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。Generator函数相当是一个状态机,封装了多个内部状态。执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历Generato
jeremyxu
2018/05/10
1.1K0
如何写出一个惊艳面试官的 Promise【近 1W字】 前言源码1.Promise2.Generator3.async 和 await4.Pro
1.高级 WEB 面试会让你手写一个Promise,Generator 的 PolyFill(一段代码); 2.在写之前我们简单回顾下他们的作用; 3.手写模块见PolyFill.
火狼1
2020/05/09
7180
如何写出一个惊艳面试官的 Promise【近 1W字】
                            前言源码1.Promise2.Generator3.async 和 await4.Pro
从Generator入手读懂 co 模块源码(干货)
本文主要会讲 Generator 的运用和实现原理,然后我们会去读一下 co 模块的源码,最后还会提一下 async/await。
coder_koala
2020/09/08
6680
从Generator入手读懂 co 模块源码(干货)
co 函数库的含义和用法
======================================== 以下是《深入掌握 ECMAScript 6 异步编程》系列文章的第三篇。 Generator函数的含义与用法 Th
ruanyf
2018/04/12
1K0
co 函数库的含义和用法
几种常见的手写源码实现
简单版深拷贝,列举三个例子 array object function,可以自行扩展。主要是引发大家的思考
前端迷
2020/02/02
9910
理解 ES6 generator
与 promise 对象类似,这里运用鸭子模型进行判断,如果对象中有 next 与 throw 两个方法,那么就认为这个对象是一个生成器对象。
翼德张
2022/01/13
2300
面试官问 async、await 函数原理是在问什么?
这周看的是 co 的源码,我对 co 比较陌生,没有了解和使用过。因此在看源码之前,我希望能大概了解 co 是什么,解决了什么问题。
若川
2021/09/27
6540
【Nodejs】994- 一文搞懂koa2核心原理
https://juejin.cn/post/6966432934756794405
pingan8787
2021/06/24
6380
社招前端经典手写面试题合集
使用时间戳的节流函数会在第一次触发事件时立即执行,以后每过 wait 秒之后才执行一次,并且最后一次触发事件不会被执行
helloworld1024
2022/10/19
7670
字节前端高频手写面试题(持续更新中)1
观察者需要放到被观察者中,被观察者的状态变化需要通知观察者 我变化了 内部也是基于发布订阅模式,收集观察者,状态变化后要主动通知观察者
helloworld1024
2023/01/03
6980
JS魔法堂:剖析源码理解Promises/A规范
一、前言                                 Promises/A是由CommonJS组织制定的异步模式编程规范,有不少库已根据该规范及后来经改进的Promises/A+规范提供了实现   如Q, Bluebird, when, rsvp.js, mmDeferred, jQuery.Deffered()等。   虽然上述实现库均以Promises/A+规范作为实现基准,但由于Promises/A+是对Promises/A规范的改进和增强,因此深入学习Promises/A规范也是
^_^肥仔John
2018/01/18
1.3K0
JS魔法堂:剖析源码理解Promises/A规范
前端异步代码解决方案实践(二)
早前有针对 Promise 的语法写过博文,不过仅限入门级别,浅尝辄止食而无味。后面一直想写 Promise 实现,碍于理解程度有限,多次下笔未能满意。一拖再拖,时至今日。
前朝楚水
2018/07/26
3.3K0
前端二面必会手写面试题
script标签不遵循同源协议,可以用来进行跨域请求,优点就是兼容性好但仅限于GET请求
helloworld1024
2022/12/20
6360
前端一面高频面试题(附答案)
我们常说的代理也就是指正向代理,正向代理的过程,它隐藏了真实的请求客户端,服务端不知道真实的客户端是谁,客户端请求的服务都被代理服务器代替来请求。
loveX001
2022/12/14
6020
源码速读!一看就会、一写就废的 Promise 实现
Promise 对于前端来说,是个老生常谈的话题,Promise 的出现解决了 js 回调地狱(Callback Hell)的问题。
Nealyang
2020/04/22
5100
图解 Promise 实现原理(三)—— Promise 原型方法实现
Promise 是异步编程的一种解决方案,它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。更多关于 Promise 的介绍请参考阮一峰老师的 ES6入门 之 Promise 对象。
2020labs小助手
2020/05/18
9480
Promise源码指南
相信许多从事前端的小伙伴们都用过Promise,为了解决异步编程的弊端(地狱回调等问题),ES6提供了一个强大的东西,那就是Promise。Promise是将异步任务转换为同步任务的一个构造函数,通过resolve,reject改变任务的状态,必不可少的then方法用来收Promise的值,这些都是Promise的基本使用。那么Promise是如何处理状态的,又是如何实现resove,reject方法的,又是如何实现链式调用的呢,如果你不知道,那么这篇文章可以帮到你,下面我们一起来解析一下Promise到底是如何实现的,相信看完这篇文章,每个人都可以写出属于自己的Promise方法。
落落落洛克
2021/04/13
6700
Promise源码指南
图解JavaScript——代码实现【2】(重点是Promise、Async、发布/订阅原理实现)
本节主要阐述六种异步方案:回调函数、事件监听、发布/订阅、Promise、Generator和Async。其中重点是发布/订阅、Promise、Async的原理实现,通过对这几点的了解,希望我们前端切
前端迷
2020/10/26
7690
图解JavaScript——代码实现【2】(重点是Promise、Async、发布/订阅原理实现)
相关推荐
Promise 源码分析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验