原文: http://pij.robinqu.me/JavaScript_Core/Functional_JavaScript/JavaScript_Generator.html
- 本文须要补充更多例子
- 本文存在讲明,但该网站的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);
});
上面离别进行了一个读文件和列目次的操纵,均是异步操纵。为了完成如许的suspend
和resume
。我们简朴的封装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变更的细节,会在以后的文章中细致说明注解。