Node.js 101(3): generator
来源:互联网 发布:网络盒子什么牌子好 编辑:程序博客网 时间:2024/05/22 09:04
原文地址——http://blog.chrisyip.im/nodejs-101-generator
之前介绍了 Promise and async,现在来说说 ECMAScript 6 新加入的 Generator。
In computer science, a generator is a special routine that can be used to control the iteration behaviour of a loop. In fact, all generators are iterators. – Wikipedia
Generator 是一种控制程序迭代的方法,让顺序执行的迭代程序变成异步执行 (此异步和 Node.js 的异步调用有差异) 。在 Node.js 里,它的存在目的就是可以写出更好的控制流。
假设有如下代码:
function loop (arr, cb) { for (let i = 0, len = arr.length; i < len; i++) { cb(arr[i]) }}
如果需要扩展这个代码,增加更多的回调:
function loop (arr, cb) { for (let i = 0, len = arr.length; i < len; i++) { for (let j = 0, jlen = cb.length; j < jlen; j++) { cb[j](arr[i]) } }}loop(arr, [cb1, cb2, cb3, cb4, ...cbN])
换成 generator 就可以这样:
function* loop (arr) { for (let i = 0, len = arr.length; i < len; i++) { yield arr[i] }}var ge = loop(['Phil Colson', 9])var name = ge.next().value , profile = getProfile(name)accessLog(name)var accessLevel = ge.next().valueif (profile.level !== accessLevel) { throw new Error('You don\'t have privilege!')}if (ge.next().done) { loginDone(profile)}
从例子可以看到,generator 和一般的函数并没有太大的区别,仅仅需要在 function
后加一个星号即可,而最大差别是,函数是直接调用并返回结果,而 generator 则是调用后,创建并返回一个 constructor 为GeneratorFunctionPrototype
的对象,然后通过它的 next
方法来让函数执行并获取 yield
语句的值:
function* ge () { yield 1 }var g = ge()g.next()// { value: 1, done: false }
yield
关键字是 generator 的核心,当 next
被调用时,将会执行 generator 内的代码,一旦遇到 yield
时将停止执行,并等待下一个next
的调用,不停重复,直到所有的代码执行完毕。这赋予了函数异步执行的能力。
function* ge () { console.log('called') yield 1 console.log('yield called')}var g = ge()g.next()// 'called'// { value: 1, done: false }g.next()// 'yield called'// { value: undefined, done: true }
yield
后面可以是值、对象、函数或表达式,next
调用时返回的value
默认是 yield
右侧语句的值,那像 var x = yield 1
、fn(yield ARGV)
这样的语句呢?
function* ge () { var x = yield 10; console.log(x) return x + 5}var g = ge()g.next()// { value: 10, done: false }g.next(1)// 1// { value: 6, done: true }g = ge()g.next()// { value: 10, done: false }g.next()// undefined// { value: NaN, done: true )
第一个 next().value
会是 yield
右侧语句的值,而第二个 next()
的参数将会传递给 yield
左侧的语句;如果 next()
没参数,将是undefined
。利用这个特性,就可以和 Promise 等组合在一起,实现更方便的功能,这点会在后面介绍。
done
标记是否所有代码已执行完毕。
上面的 loop
经改造成 generator 后,三个 ge.next()
分别返回:
{ value: 'Phil Colson', done: false }
{ value: 9, done: false }
{ value: undefined, done: true }
注意,最后一个 next
被调用时,它的 value
等于 generator function 的返回值,如无 return
,将会是 undefined
:
(function* demo () { return 'demo'})().next()// { value: 'demo', done: true }
Generator 的基础介绍完毕,开始利用 generator 改造 readFiles。
首先把 generator 封装成返回 Promise 的函数:
// https://www.promisejs.org/generators/function async (makeGenerator) { return function () { var generator = makeGenerator.apply(null, arguments); function handle(result){ if (result.done) { return Promise.resolve(result.value); } return Promise.resolve(result.value).then(function (res){ return handle(generator.next(res)); }, function (err){ return handle(generator.throw(err)); }); } try { return handle(generator.next()); } catch (ex) { return Promise.reject(ex); } };}
async
通过 next()
把 value
给传递到下一个 Promise.resolve
里,再通过 next(res)
把结果传递到 yield
的左边,从而让程序可以像一个同步运行的程序一样。不停重复这几步,直到 next().done === true
为止。
接着要把 fs
的接口封装成 Promise 模式,这里使用了 fs-extra 和bluebird 这两个库:
const Promise = require('bluebird'), fs = Promise.promisifyAll(require('fs-extra'))
经过 bluebird.promisifyAll
处理的对象的所有函数将被封装成 Promise 模式,封装后的函数一般会在函数名后面增加 Async
,如fs.readdirAsync
。
经过这两步处理,readFiles
就可以改造成如下所示 (为了缩减代码行数,移除了过滤功能) :
function* readFiles (opts) { var res = [], files files = yield fs.readdirAsync(opts.folder); for (var i = 0, len = files.length; i < len; i++) { var file = files[i], fullPath = path.resolve(opts.folder + '/' + file).replace(process.cwd(), '.'), stat = yield fs.statAsync(fullPath) if (stat.isDirectory()) { res = res.concat( yield async(readFiles)(_.assign({}, opts, { folder: fullPath })) ) } else if (opts.pattern.test(file)) { res.push(fullPath) } } return res}function Sagase () { Object.defineProperties( this, { find: { enumerable: true, value: function (opts) { return async(readFiles)(formatOptions(opts)) } } } )}
经过这样的处理后,readFiles
少了一些多余的嵌套,又保留了该有的异步能力。
Generator 的概念其实就是惰性求值,通过暂停函数执行来让开发者控制程序的控制流,而不是像及早求值那样,语句到那里了就需要立刻求出值。
Generator 并不是用来代替别的控制流的灵丹妙药,它只是一种可能性,让你写出更好的代码的可能性,Koa 的官方例子就很好地解释了这一点:
是不是很像 DOM 的冒泡事件呢?
长按图片识别图中二维码(或搜索微信公众号FrontEndStory)关注“前端那些事儿”,带你了解最新的前端技术。
- Node.js 101(3): generator
- Node.js之express生成器express-generator
- JS(三)generator
- Node.js es6 generator 和 thunk 函数解决异步金字塔
- node.js 任务5 使用express generator快速创建应用
- node.js笔记(3)
- JS之Generator(生成器)
- Node.js(3) -- 七天学会node.js
- 理解node.js(Understanding node.js)
- node与ES6系列3——generator对象
- node.js(3) 模块加载机制
- node.js学习日记(3)
- Node.js先睹为快(3)
- node.js-3
- Node.js[3] Buffer
- node.js(一)
- Node.js(一)
- Node.js(一)
- 设置 ubuntu sudo 时不用输密码
- Oracle数据库综合实战
- spring mvc
- UVA Knight Moves
- 不同设备不同界面:Windows 9正在逼近!
- Node.js 101(3): generator
- Thinkphp里import的几个用法介绍
- java 数组常见的几种排序
- FreeDOS诞生20周年
- Node.js 101(4):Grunt and gulp.js
- StringBuffer类和String
- APMserv 5.2.6 搭建的ThinkPHP网站如何开启伪静态.htaccess?
- USB驱动之 端点 endpoint
- 天猫的评分的样式