让人头痛的Generator 函数的异步应用真的有用吗?( 五 )
var fs = require('fs');var readFile = function (fileName){return new Promise(function (resolve, reject){fs.readFile(fileName, function(error, data){if (error) return 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方法 , 包装成onFulfilled函数 。 这主要是为了能够捕捉抛出的错误 。
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 = http://kandian.youth.cn/index/toPromise.call(ctx, ret.value);if (valuereturn 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 支持并发的异步操作 , 即允许某些操作同时进行 , 等到它们全部完成 , 才进行下一步 。
这时 , 要把并发的操作都放在数组或对象里面 , 跟在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(function* () {var values = [n1, n2, n3];yield values.map(somethingAsync);});function* somethingAsync(x) {// do something asyncreturn y}上面的代码允许并发三个somethingAsync异步操作 , 等到它们全部完成 , 才会进行下一步 。
实例:处理 StreamNode 提供 Stream 模式读写数据 , 特点是一次只处理数据的一部分 , 数据分成一块块依次处理 , 就好像“数据流”一样 。 这对于处理大规模数据非常有利 。 Stream 模式使用 EventEmitter API , 会释放三个事件 。
- data事件:下一块数据块已经准备好了 。
- end事件:整个“数据流”处理完了 。
- error事件:发生错误 。
const co = require('co');const fs = require('fs');const stream = fs.createReadStream('./les_miserables.txt');let valjeanCount = 0;co(function*() {while(true) {const res = yield Promise.race([new Promise(resolve => stream.once('data', resolve)),new Promise(resolve => stream.once('end', resolve)),new Promise((resolve, reject) => stream.once('error', reject))]);if (!res) {break;}stream.removeAllListeners('data');stream.removeAllListeners('end');stream.removeAllListeners('error');valjeanCount += (res.toString().match(/valjean/ig) || []).length;}console.log('count:', valjeanCount); // count: 1120});
- 智能手机市场|华为再拿第一!27%的份额领跑全行业,苹果8%排在第四名!
- 会员|美容院使用会员管理软件给顾客更好的消费体验!
- 行业|现在行业内客服托管费用是怎么算的
- 人民币|天猫国际新增“服务大类”,知舟集团提醒入驻这些类目的要注意
- 国外|坐拥77件专利,打破国外的垄断,造出中国最先进的家电芯片
- 技术|做“视频”绿厂是专业的,这项技术获人民日报评论点赞
- 面临|“熟悉的陌生人”不该被边缘化
- 中国|浅谈5G移动通信技术的前世和今生
- 页面|如何简单、快速制作流程图?上班族的画图技巧get
- 桌面|日常使用的软件及网站分享 篇一:几个动态壁纸软件和静态壁纸网站:助你美化你的桌面
