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 1fn(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() 分别返回:

  1. { value: 'Phil Colson', done: false }
  2. { value: 9, done: false }
  3. { 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 的官方例子就很好地解释了这一点:

How generators work in Koa

是不是很像 DOM 的冒泡事件呢?

长按图片识别图中二维码(或搜索微信公众号FrontEndStory)关注“前端那些事儿”,带你了解最新的前端技术。


0 0
原创粉丝点击