Javascript Generator - 函数式编程 - Javascript中心

原文: http://pij.robinqu.me/JavaScript_Core/Functional_JavaScript/JavaScript_Generator.html

源代码: https://github.com/RobinQu/Programing-In-JavaScript/blob/master/chapters/JavaScript_Core/Functional_JavaScript/JavasSript_Generator.md

  • 本文须要补充更多例子
  • 本文存在讲明,但该网站的Markdown编辑器不支撑,所以没法一般展现,请到原文参考。

Javascript Generator

ES6中的Generator的引入,极大程度上转变了Javascript程序员对迭代器的意见,并为处理callback hell1供应了新要领。

Generator是一个与言语无关的特征,理论上它应当存在于一切Javascript引擎内,然则现在真正完全完成的,只要在node --harmony 下。所以后文一切的诠释,都以node环境举例,须要的启动参数为node --harmony --use_strict

V8中所完成的Generator和规范当中说的又有区分,这个可以参考一下MDC的相干文档2。而且,V8在写作这篇文章时,并没有完成Iterator。

用作迭代器

我们以一个简朴的例子3最先:

function* argumentsGenerator() {
  for (let i = 0; i < arguments.length; i += 1) {
    yield arguments[i];
  }
}

我们愿望迭代传入的每一个实参:

var argumentsIterator = argumentsGenerator('a', 'b', 'c');

// Prints "a b c"
console.log(
    argumentsIterator.next().value,
    argumentsIterator.next().value,
    argumentsIterator.next().value
);

我们可以简朴的邃晓:

  • Generator现实上是天生Iterator的要领。argumentsGenerator被称为GeneartorFunction,也有些人把GeneartorFunction的返回值称为一个Geneartor
  • yield可以中缀GeneartorFunction的运转;而在下一次yield时,可以恢复运转。
  • 返回的Iterator上,有next成员要领,可以返回迭代值。个中value属性包括现实返回的数值,done属性为布尔值,标记迭代器是不是完成迭代。要注重的是,在done属性为true后继承运转next要领会发作异常。

完全的ES完成中,for-of轮回恰是为了疾速迭代一个iterator的:

// Prints "a", "b", "c"
for(let value of argumentsIterator) {
  console.log(value);
}

惋惜,现在版本的node不支撑for-of

说到这里,大多数有履历的Javascript程序员会示意不屑,由于这些都可以经由历程本身编写一个函数来完成。我们再来看一个例子:

function* fibonacci() {
  let a = 0, b = 1;
  //1, 2
  while(true) {
    yield a;
    a = b;
    b = a + b;
  }
}

for(let value of fibonacci()) {
  console.log(value);
}

fibonacci序列是无限的数字序列,你可以用函数的迭代来天生,然则远没有效Generator来的简约。

再来个更风趣的。我们可以应用yield*语法,将yield操纵代理到别的一个Generator

let delegatedIterator = (function* () {
  yield 'Hello!';
  yield 'Bye!';
}());

let delegatingIterator = (function* () {
  yield 'Greetings!';
  yield* delegatedIterator;
  yield 'Ok, bye.';
}());

// Prints "Greetings!", "Hello!", "Bye!", "Ok, bye."
for(let value of delegatingIterator) {
  console.log(value);
}

用作流程掌握

yield可以停息运转流程,那末便为转变实行流程供应了能够4。这和Python的coroutine相似。

co已将此特征封装的异常完美了。我们在这里简朴的议论其完成。

The classic example of this is consumer-producer relationships: generators that produce values, and then consumers that use them. The two generators are said to be symmetric – a continuous evaluation where coroutines yield to each other, rather than two functions that call each other.

Geneartor之所以可用来掌握代码流程,就是经由历程yield来将两个或许多个Geneartor的实行途径相互切换。这类切换是语句级别的,而不是函数挪用级别的。其实质是CPS幻化,后文会给出诠释。

这里要补充yield的多少行动:

  • next要领接收一个参数,传入的参数是yield表达式的返回值;即yield既可以发作数值,也可以接收数值
  • throw要领会抛出一个异常,并停止迭代
  • GeneratorFunction的return语句等同于一个yield

将异步“变”为同步

假定我们愿望有以下语法作风:

  • suspend传入一个GeneratorFunction
  • suspend返回一个简朴的函数,接收一个node作风的回调函数
  • 一切的异步挪用都经由历程yield,看起来像同步挪用
  • 给定一个特别的回调,让保证异步挪用的返回值作为yield的返回值,而且让剧本继承
  • GeneratorFunction的返回值和实行历程的毛病都会会传入全局的回调函数

更细致的,以下例子:

var fs = require("fs");
suspend(function*(resume) {
  var content = yield fs.readFile(__filename, resume);
  var list = yield fs.readdir(__dirname, resume);
  return [content, list];
})(function(e, res) {
  console.log(e,res);
});

上面离别进行了一个读文件和列目次的操纵,均是异步操纵。为了完成如许的suspendresume。我们简朴的封装Generator的API:

var slice = Array.prototype.slice.call.bind(Array.prototype.slice);

var suspend = function(gen) {//`gen` is a generator function
  return function(callback) {
    var args, iterator, next, ctx, done;
    ctx = this;
    args = slice(arguments);

    next = function(e) {
      if(e) {//throw up or send to callback
        return callback ? callback(e) : iterator.throw(e);
      }
      var ret = iterator.next(slice(arguments, 1));
      if(ret.done && callback) {//run callback is needed
        callback(null, ret.value);
      }
    };

    resume = function(e) {
      next.apply(ctx, arguments);
    };

    args.unshift(resume);
    iterator = gen.apply(this, args);
    next();//kickoff
  };
};

有容乃大

现在我们只支撑回调情势的API,而且须要显现的传入resume作为API的回调。为了像co那样支撑更多的可以作为yield参数。co中,作者将一切情势的异步对象都归结为一种名为thunk的回调情势。

那什么是thunk呢?thunk就是支撑规范的node作风回调的一个函数: fn(callback)

起首我们将suspend修改成自动resume:

var slice = Array.prototype.slice.call.bind(Array.prototype.slice);

var suspend = function(gen) {
  return function(callback) {
    var args, iterator, next, ctx, done;
    ctx = this;
    args = slice(arguments);
    next = function(e) {
      if(e) {
        return callback ? callback(e) : iterator.throw(e);
      }
      var ret = iterator.next(slice(arguments, 1));

      if(ret.done && callback) {
        return callback(null, ret.value);
      }

      if("function" === typeof ret.value) {//shold yield a thunk
        ret.value.call(ctx, function() {//resume function
          next.apply(ctx, arguments);
        });
      }

    };

    iterator = gen.apply(this, args);
    next();
  };
};

注重,这个时刻,我们只能yield一个thunk,我们的运用要领也要发作转变:

var fs = require("fs");
read = function(filename) {//wrap native API to a thunk
  return function(callback) {
    fs.readFile(filename, callback);
  };
};

suspend(function*() {//return value of this generator function is passed to callback
  return yield read(__filename);
})(function(e, res) {
  console.log(e,res);
});

接下来,我们要让这个suspend越发有效,我们可以支撑以下内容穿入到yield

  • GeneratorFunction
  • Generator
  • Thunk
var slice = Array.prototype.slice.call.bind(Array.prototype.slice);

var isGeneratorFunction = function(obj) {
  return obj && obj.constructor && "GeneratorFunction" == obj.constructor.name;
};

var isGenerator = function(obj) {
  return obj && "function" == typeof obj.next && "function" == typeof obj.throw;
};

var suspend = function(gen) {
  return function(callback) {
    var args, iterator, next, ctx, done, thunk;
    ctx = this;
    args = slice(arguments);
    next = function(e) {
      if(e) {
        return callback ? callback(e) : iterator.throw(e);
      }
      var ret = iterator.next(slice(arguments, 1));

      if(ret.done && callback) {
        return callback(null, ret.value);
      }

      if(isGeneratorFunction(ret.value)) {//check if it's a generator
        thunk = suspend(ret.value);
      } else if("function" === typeof ret.value) {//shold yield a thunk
        thunk = ret.value;
      } else if(isGenerator(ret.value)) {
        thunk = suspend(ret.value);
      }

      thunk.call(ctx, function() {//resume function
        next.apply(ctx, arguments);
      });

    };

    if(isGeneratorFunction(gen)) {
      iterator = gen.apply(this, args);
    } else {//assume it's a iterator
      iterator = gen;
    }
    next();
  };
};

在运用时,我们可以传入三种对象到yield:

var fs = require("fs");
read = function(filename) {
  return function(callback) {
    fs.readFile(filename, callback);
  };
};

var read1 = function*() {
  return yield read(__filename);
};

var read2 = function*() {
  return yield read(__filename);
};

suspend(function*() {
  var one = yield read1;
  var two = yield read2();
  var three = yield read(__filename);
  return [one, two, three];
})(function(e, res) {
  console.log(e,res);
});

固然,到这里,人人应当都邃晓怎样让suspend兼容更多的数据类型,比方Promise、数组等。但更多的扩大,在这里就不再赘述。这里的suspend可以就说就是精简的co了。

yield的引入,让流程掌握走上了一条平坦大路,不须要运用庞杂的Promise、也不必运用丢脸的async。同时,从机能角度,yield可以经由历程V8的后续优化,机能进一步提拔,现在来讲yield的机能并不差5

yield的转换

yield的实质是一个语法糖,底层的完成体式格局就是CPS变更6。也就是说yield是可以用轮回和递归从新完成的,基础用不着一定在V8层面完成。但笔者以为,纯Javascript完成的”yield”会形成大批的客栈斲丧,在机能上毫无上风可言。从机能上斟酌,V8可以优化yield的编译,完成更高机能的转换。

关于CPS变更的细节,会在以后的文章中细致说明注解。

  1. http://callbackhell.com/ 

  2. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators 

  3. https://github.com/JustinDrake/node-es6-examples#generators 

  4. http://dailyjs.com/2013/05/31/suspend/ 

  5. http://dailyjs.com/2013/10/17/yield/ 

  6. http://en.wikipedia.org/wiki/Continuation-passing_style 

    原文作者:RobinQu
    原文地址: https://segmentfault.com/a/1190000000515173
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞