JavaScript co 函数库原理与用法详解

作者:函数茶馆时间:2026-10-24 04:21:52浏览:557

本文详解JS co函数库的含义与用法。co用于自动执行Generator函数,结合Thunk和Promise两种异步处理方式。通过源码分析,阐述其基于Promise状态控制next执行的核心原理,并展示并发异步操作的实现方式,帮助开发者深入理解Node.js中的异步编程模式。

co函数库概述

本文详细介绍JS co函数库的含义与具体用法,供开发者参考。

本文是阮一峰老师异步编程系列教程的第三部分,重点讲解co。

co函数库在Node.js开发中应用广泛,虽然源码结构简洁,但其设计思路非常精妙。

此前因离职事务繁忙,未能及时深入研读,现已有空闲时间进行补充学习。

目前工作已步入正轨,正在适应新公司的节奏,利用周末时间继续深入技术学习。

回顾前两篇文章,分别介绍了Generator函数和Thunk方式的自动执行机制。

本文将延续之前的思路,探讨如何利用co工具实现Generator函数的自动执行。

co实现Generator自动执行

首先回顾上一节使用的示例代码:


var fs = require('fs');
var thunkify = require('thunkify');
var readFile = thunkify(fs.readFile);
 
var gen = function* (){
 var r1 = yield readFile('/etc/fstab');
 console.log(r1.toString());
 var r2 = yield readFile('/etc/shells');
 console.log(r2.toString());
};

该代码用于读取两个文件,接下来探讨如何使用co工具实现其自动执行。

实现方式非常简单:


var co = require('co');
co(gen);

只需将Generator函数作为参数传入co函数,即可实现自动执行。

co函数返回一个Promise对象,因此可以使用then方法添加回调函数。当Generator执行结束时,将触发该回调:


co(gen).then(function (){
 //success
});

从表面看,这似乎与之前的Thunk方式相似,只是将run函数名替换为co。

两者唯一的区别在于多了一层Promise包装,那么co与Thunk的具体区别在哪里?

正如推测,co实际上是将之前的两种自动执行方式(Thunk和Promise)结合,封装为一个库。

其使用前提比Thunk多了一种情况,即yield返回值必须是Thunk函数或Promise对象之一。

co实现原理与源码分析

接下来分析其实现原理:

在研究Thunk函数时,第一步是使用thunkify将readFile包装为Thunk函数:

若研究Promise,则需将readFile包装为Promise对象,代码如下:


var fs = require('fs');
 
var readFile = function (fileName){
 return new Promise(function (resolve, reject){
  fs.readFile(fileName, function(error, data){
   if (error) reject(error);
   resolve(data);
  });
 });
};
 
var gen = function* (){
 var f1 = yield readFile('/etc/fstab');
 var f2 = yield readFile('/etc/shells');
 console.log(f1.toString());
 console.log(f2.toString());
};

接下来展示作者推崇的做法:先用最原始的方式手动执行一遍代码。

在日常开发中,当逻辑复杂时,建议先以最直白的方式实现,

再从中发现规律并进行优化重构。以下是手动执行的代码示例:


var g = gen();
 
g.next().value.then(function(data){
 g.next(data).value.then(function(data){
  g.next(data);
 });
});

手动执行的核心是利用then方法层层添加回调函数。理解这一点后,即可编写自动执行器:


function run(gen){
 var g = gen();  //开始执行
 
 function next(data){
  var result = g.next(data);
  if (result.done) return result.value;
  result.value.then(function(data){
   next(data);
  });
 }
 next();
}
run(gen);

与Thunk函数的区别在于,Thunk函数在执行成功后将next传给thunkify,由thunkify协助执行next。

而co的做法是将next交给Promise,由Promise控制请求成功时执行next。区别仅在此处。

分析完原理,接下来深入分析co的源码:

co函数接受一个Generator参数,并返回一个Promise对象。


function co(gen) {
 var ctx = this;
 
 return new Promise(function(resolve, reject) {
 });
}

在返回的Promise对象内部,首先检查参数gen是否为Generator函数。如果是,则执行该函数,得到一个内部指针对象;

如果不是,则直接返回,并将Promise对象的状态改为resolved。


function co(gen) {
 var ctx = this;
 
 return new Promise(function(resolve, reject) {
  if (typeof gen === 'function') gen = gen.call(ctx);
  if (!gen || typeof gen.next !== 'function') return resolve(gen);
 });
}

接着,co对next方法进行包装,以便暴露异常:


function co(gen) {
 var ctx = this;
 
 return new Promise(function(resolve, reject) {
  if (typeof gen === 'function') gen = gen.call(ctx);
  if (!gen || typeof gen.next !== 'function') return resolve(gen);
 
  onFulfilled();
  function onFulfilled(res) {
   var ret;
   try {
    ret = gen.next(res);
   } catch (e) {
    return reject(e);
   }
   next(ret);
  }  
 });
}

最后是next方法的实现:


function next(ret) {
 if (ret.done) return resolve(ret.value);
 var value = toPromise.call(ctx, ret.value);
 if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
 return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
    + 'but the following object was passed: "' + String(ret.value) + '"'));
  }
});

next方法中最关键的一行代码:


if (value && isPromise(value)) return value.then(onFulfilled, onRejected);

co的使用条件是,每次yield返回必须是Promise对象,当Promise处理成功时,再次执行onFulfilled。

以此达到自动执行的效果。

并发异步操作支持

co还支持并发的异步操作,yield返回一个数组或支持遍历的对象即可:


// 数组的写法
co(function* () {
 var res = yield [
  Promise.resolve(1),
  Promise.resolve(2)
 ];
 console.log(res); 
}).catch(onerror);
 
// 对象的写法
co(function* () {
 var res = yield {
  1: Promise.resolve(1),
  2: Promise.resolve(2),
 };
 console.log(res); 
}).catch(onerror);

至此,co的自动执行原理学习完毕。

其本质并不复杂,只是涉及的模块较多,思路较为灵活。

对于笔者而言,目前的理解程度约为90%。

因此,本文主要复述老师的思路,个人思想较少。

最后附上原文地址:co 函数库的含义和用法。

感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun测试上述代码运行效果。

更多关于JavaScript相关内容可查看本站专题:《JavaScript常用函数技巧汇总》、《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》及《JavaScript数学运算用法总结》。

希望本文所述对大家JavaScript程序设计有所帮助。

作者最新文章