JS co 函数库的含义和用法实例总结
本文实例讲述了JS co 函数库的含义和用法。分享给大家供大家参考,具体如下:
继续学习阮一峰老师异步编程四部曲之三:co
co在很早之前就听超哥讲过,说在node编程中大量用到,源码很简单,但是想法很强大。
让我有空抓紧了解下,前一段时间弄离职的事情,跑来跑去累的够呛。
现在终于一切回归正常了,还在拼命的适应新公司的节奏。只能趁周末继续学习了。
好了,不瞎扯了,回归主题,前两篇文章我们分别学习了 Generator 函数和 Thunk 方式的自动执行。
今天我们接着上次的思路学习使用 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多了一种 Promise 的情况,也就是yield返回值必须是 Thunk 函数和 Promise 对象之一
实现原理:
之前我们在研究 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
这里的做法是吧 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对象,当promist处理成功时再次执行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%
所以这篇文章里面自己的思想比较少,更多的还是在复述老师的思路。
最后贴上原文的地址:
感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:测试上述代码运行效果。
更多关于JavaScript相关内容可查看本站专题:《》、《》、《》、《》及《》
希望本文所述对大家JavaScript程序设计有所帮助。