博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[Node.js] ECMAScript 6中的生成器及koa小析
阅读量:5082 次
发布时间:2019-06-13

本文共 3785 字,大约阅读时间需要 12 分钟。

原文地址:

引子

koa老听人说 大法好,这两天我也赶了把时髦:用  安上了node 0.11.12,下了个koa开启harmony模式试水。在一系列文档和贴子的教育下,大概认识到:

  • koa 是大神主导的新一代Web框架
  • koa 的中间件基于ES6的生成器函数(function *)形式
  • koa的核心流程库是 ,它能很好的解决问题

在接触 Node.js 前,由于有过 Python编程的经验,我对生成器是个什么东西已经是很清楚了。我真正感兴趣的是:它是怎么被用来优化回调嵌套的。

“为何这么屌”

在  一文中有这么一段(片断1):

var fs = require('fs');var app = require('koa')();var readFile = function(dir) {    return function(fn) {        fs.readFile(dir, fn);    }}app.use(function* () {  var arr = yield ['1.txt', '2.txt', '3.txt'].map(function(path) {        return readFile(path);    });    this.body = arr.join(',');})app.listen(8000);

 

这段代码很好的演示了koa是如何利用生成器函数(,函数的constructor.name === 'GeneratorFunction' )来串行化异步回调的,它的执行流程:

  1. function*(){...} 被做为生成器函数push到了koa的中间件队列中
  2. koa使用co框架,对这个生成器函数进行调用执行;执行生成器函数并不立即执行函数体,而是生成生成器(generator)实例——同时,生成器可视为遵循的实例,每次调用迭代器的next(),都会返回一个{ value: obj, done:true/false }对象:value是执行结果值,done指示迭代是否完成
  3. 调用生成器实例的next()方法将启动函数体内部的执行流,直到出现yield时被挂起,那么下一次的next()将会给yield返回执行的结果值,并挂起在下一个yield出现处;可以理解为,yield总是返回上一次next()的结果值,如果next()有参数,yield将返回这个参数值(异步回调有机会注入执行结果)
  4. 观察上面代码片段我们注意到,readFile的回调处理函数fn代码中并未提供,那么koa或者说co是怎么处理这个回调函数的呢?在 koa(0.13.0)自带的co源码(line:84)中,可以看到如下片断(片断2):
    // normalizeret.value = toThunk(ret.value, ctx);// runif ('function' == typeof ret.value) {  var called = false;  try {    ret.value.call(ctx, function(){      if (called) return;      called = true;      next.apply(ctx, arguments);    });  } catch (e) {    setImmediate(function(){      if (called) return;      called = true;      next(e);    });  }  return;}
  5. toThunk 会根据 yield返回的表达式转换成标准函数(片断3):
    function toThunk(obj, ctx) {  if (isGeneratorFunction(obj)) {    return co(obj.call(ctx));  }  if (isGenerator(obj)) {    return co(obj);  }  if (isPromise(obj)) {    return promiseToThunk(obj);  }  if ('function' == typeof obj) {    return obj;  }  if (isObject(obj) || Array.isArray(obj)) {    return objectToThunk.call(ctx, obj);  }  return obj;}
  6. 在 中,生成器函数首先返回的是一个生成器;然后,yield 结合map会返回三个function对象,即高阶函数readFile返回的function:
    function(fn) {    fs.readFile(dir, fn);}
  7.  根据  返回的类型,对 function 进行了call调用,并提供了回调函数:将arguments通过next.apply(ctx, arguments);巧妙的进行传递。如前所述,next()如果提供了参数,yield得到的结果值就是这个参数,回调结果由此而来。

到底是谁屌

如果看官看完上面那几段还没晕,那当然是您最屌:) ——我的表述能力确实不足以很清晰的道出框架的玄机,但在我看来,真正屌的是ES6 Generator机制本身。

暂时放下 co 框架,把  稍加改造(片断4):

var fs = require('fs');var path = require('path');    var readFile = function (dir) {    return function (fn) {        fs.readFile(dir, {encoding: 'utf8', flag: 'r'}, fn);    };};    function *readFileGeneratorFunction(path, cb){    console.log(yield readFile(path)(cb));}    var readFileIterator = readFileGeneratorFunction('testDate.js', callback);function callback(err, data){    readFileIterator.next(data);}readFileIterator.next();

 用意很明显:

  1. 这个readFileGeneratorFunction就是个生成器函数,执行它返回一个生成器(迭代器)
  2. 高阶函数返回的function,在生成器函数执行时指定了回调
  3. next触发执行
  4. 回调完成时,next(data)携带结果值触发yield

问题也很明显,业务代码(GeneratorFunction中的yield) 需要前置于流程控制(callback),这不科学。抽象一下,可以提供一个生成器函数的执行函数:

var slice = Array.prototype.slice;function run(generatorFunction) {    try {        var generatorItr = generatorFunction(callback);        function callback(err, res) {            if(err)                generatorItr.throw(err);            else {                var args = slice.call(arguments, 1);                res = args.length > 1 ? args : res;                generatorItr.next(res);            }        }        generatorItr.next();    }    catch (e){        console.log(e.message | "I'm died.");    }}; 测试一下:run(function* rfGenFunc(cb) {    console.log('first');    console.log(yield readFile('1.txt')(cb));    console.log('second');    console.log(yield readFile('2.txt')(cb));});

 执行结果:

小结

本文仅对Generator的next()应用进行了简单的描述(其实它还有更多内容如:throw/send/close),抛砖引玉罢了。至于生成器特性,目前仍处于 ECMAScript 6 规范草案中,如所言:请谨慎使用 :)

 

更多文章请移步我的blog新地址:   

转载于:https://www.cnblogs.com/moye/p/ecmascript-6-generator.html

你可能感兴趣的文章
AngularJS学习篇(一)
查看>>
关于Xshell无法连接centos6.4的问题
查看>>
spring security 11种过滤器介绍
查看>>
代码实现导航栏分割线
查看>>
大数据学习系列(8)-- WordCount+Block+Split+Shuffle+Map+Reduce技术详解
查看>>
Mysql性能调优
查看>>
ES6内置方法find 和 filter的区别在哪
查看>>
Android实现 ScrollView + ListView无滚动条滚动
查看>>
硬件笔记之Thinkpad T470P更换2K屏幕
查看>>
getElement的几中属性介绍
查看>>
HTML列表,表格与媒体元素
查看>>
设计器 和后台代码的转换 快捷键
查看>>
STL容器之vector
查看>>
数据中心虚拟化技术
查看>>
复习文件操作
查看>>
SQL Server 使用作业设置定时任务之一(转载)
查看>>
第二阶段冲刺-01
查看>>
BZOJ1045 HAOI2008 糖果传递
查看>>
JavaScript 克隆数组
查看>>
eggs
查看>>