co 函数库的含义和用法

简介:

以下是《深入掌握 ECMAScript 6 异步编程》系列文章的第三篇。

一、什么是 co 函数库?

co 函数库是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行。

比如,有一个 Generator 函数,用于依次读取两个文件。

 var gen = function* (){ var f1 = yield readFile('/etc/fstab'); var f2 = yield readFile('/etc/shells');
 console.log(f1.toString());
 console.log(f2.toString()); }; 

co 函数库可以让你不用编写 Generator 函数的执行器。

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

上面代码中,Generator 函数只要传入 co 函数,就会自动执行。

co 函数返回一个 Promise 对象,因此可以用 then 方法添加回调函数。

 co(gen).then(function (){
 console.log('Generator 函数执行完成'); }) 

上面代码中,等到 Generator 函数执行结束,就会输出一行提示。

二、 co 函数库的原理

为什么 co 可以自动执行 Generator 函数?

前面文章说过,Generator 函数就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。

两种方法可以做到这一点。

(1)回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。

(2)Promise 对象。将异步操作包装成 Promise 对象,用 then 方法交回执行权。

co 函数库其实就是将两种自动执行器(Thunk 函数和 Promise 对象),包装成一个库。使用 co 的前提条件是,Generator 函数的 yield 命令后面,只能是 Thunk 函数或 Promise 对象。

上一篇文章已经介绍了基于 Thunk 函数的自动执行器。下面来看,基于 Promise 对象的自动执行器。这是理解 co 函数库必须的。

三、基于 Promise 对象的自动执行

还是沿用上面的例子。首先,把 fs 模块的 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()); }; 

然后,手动执行上面的 Generator 函数。

 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); 

上面代码中,只要 Generator 函数还没执行到最后一步,next 函数就调用自身,以此实现自动执行。

四、co 函数库的源码

co 就是上面那个自动执行器的扩展,它的源码只有几十行,非常简单。

首先,co 函数接受 Generator 函数作为参数,返回一个 Promise 对象。

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

在返回的 Promise 对象里面,co 先检查参数 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 将 Generator 函数的内部指针对象的 next 方法,包装成 onFulefilled 函数。这主要是为了能够捕捉抛出的错误。

 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 函数的内部代码,一共只有四行命令。

第一行,检查当前是否为 Generator 函数的最后一步,如果是就返回。

第二行,确保每一步的返回值,是 Promise 对象。

第三行,使用 then 方法,为返回值加上回调函数,然后通过 onFulfilled 函数再次调用 next 函数。

第四行,在参数不符合要求的情况下(参数非 Thunk 函数和 Promise 对象),将 Promise 对象的状态改为 rejected,从而终止执行。

五、并发的异步操作

co 支持并发的异步操作,即允许某些操作同时进行,等到它们全部完成,才进行下一步。

这时,要把并发的操作都放在数组或对象里面。


// 数组的写法
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); 

(完)

目录
相关文章
|
XML 存储 JSON
Android Jetpack组件 DataStore的使用和简单封装
Android Jetpack组件 DataStore的使用和简单封装
1597 0
Android Jetpack组件 DataStore的使用和简单封装
|
人工智能 物联网 大数据
阿里云ACP值不值得考?考试难不难?
最近很多人都来问小编阿里云认证的事情,他们想找一个能帮自己在职场上立足的好帮手,而阿里云可以说是不二选择。作为现在国内最具含金量的证书,其含金量和受认可程度是非常高的,越来越多的人都选择这个证书。
996 0
阿里云ACP值不值得考?考试难不难?
|
数据可视化 算法 数据挖掘
用傅里叶变换解码时间序列:从频域视角解析季节性模式
本文介绍了如何使用傅里叶变换和周期图分析来识别时间序列中的季节性模式,特别是在能源消耗数据中。通过Python实现傅里叶变换和周期图,可以有效提取并量化时间序列中的主要和次要频率成分,克服传统可视化分析的局限性。这对于准确捕捉时间序列中的季节性变化具有重要意义。文章以AEP能源消耗数据为例,展示了如何应用这些方法识别日、周、半年等周期模式。
566 3
用傅里叶变换解码时间序列:从频域视角解析季节性模式
|
Web App开发 JavaScript 前端开发
构建高效后端服务:Node.js与Express框架的实战指南
【9月更文挑战第6天】在数字化时代的潮流中,后端开发作为支撑现代Web和移动应用的核心,其重要性不言而喻。本文将深入浅出地介绍如何使用Node.js及其流行的框架Express来搭建一个高效、可扩展的后端服务。通过具体的代码示例和实践技巧,我们将探索如何利用这两个强大的工具提升开发效率和应用性能。无论你是后端开发的新手还是希望提高现有项目质量的老手,这篇文章都将为你提供有价值的见解和指导。
|
SQL 关系型数据库 MySQL
使用OceanBase进行大规模数据迁移的最佳实践
【8月更文第31天】随着业务的不断扩展,数据迁移成为了企业日常运营中不可避免的任务之一。对于那些正在从传统的数据库系统向分布式数据库系统过渡的企业来说,数据迁移尤为重要。OceanBase 是一个由阿里巴巴集团开发的高性能分布式关系数据库,它以其高可用性、水平扩展能力和成本效益而闻名。本文将探讨如何使用 OceanBase 进行大规模数据迁移,并提供相关的最佳实践和代码示例。
1151 1
|
存储 数据管理 Linux
探索Linux中的mv命令:文件移动的利器
`mv`命令在Linux中用于移动文件和目录或重命名,是数据管理和组织的关键工具。它支持交互式(-i)、详细(-v)、强制(-f)等模式,以及备份(--backup)选项。例如,`mv source.txt destination.txt`重命名文件,`mv file.txt directory/`移动文件。使用时注意目标文件的存在可能覆盖源文件,谨慎使用 `-f` 选项,确保有适当权限,并备份重要数据。
|
Ubuntu Linux 数据库
教你几招在 Linux 中高效地查找目录
教你几招在 Linux 中高效地查找目录
597 1
教你几招在 Linux 中高效地查找目录
|
JSON 测试技术 持续交付
自动化测试与脚本编写:Python实践指南
【4月更文挑战第9天】本文探讨了Python在自动化测试中的应用,强调其作为热门选择的原因。Python拥有丰富的测试框架(如unittest、pytest、nose)以支持自动化测试,简化测试用例的编写与维护。示例展示了使用unittest进行单元测试的基本步骤。此外,Python还适用于集成测试、系统测试等,提供模拟外部系统行为的工具。在脚本编写实践中,Python的灵活语法和强大库(如os、shutil、sqlite3、json)助力执行复杂测试任务。同时,Python支持并发、分布式执行及与Jenkins、Travis CI等持续集成工具的集成,提升测试效率和质量。
811 3
|
安全 网络安全 数据安全/隐私保护
BUUCTF:Misc 解析(一)
BUUCTF:Misc 解析(一)