关于迭代器(Iterator)与生成器(Generator)的知识,请阅读阮一峰老师的书 ECMAScript 6 入门
下面就开始介绍高级迭代器
1.给迭代器传递参数
给迭代器的next()方法传递参数,则这个参数的值就会替代生成器内部上一条yield语句的返回值。
要点:yield表达式本身没有返回值。yield表达式左边的值等于next方法所带的参数。next方法没有带参数,自然是undefined
function* createIterator() {
let first = yield 'welcome';
let second = yield first + 'world';
yield second + 'hard';
}
//next方法不带参数
let iterator = createIterator();
console.log(iterator.next());// { value: 'welcome', done: false }
console.log(iterator.next());// { value: 'undefinedworld', done: false }
console.log(iterator.next()); // { value: 'undefinedhard', done: false }
console.log(iterator.next());//{ value: undefined, done: true }
//next方法带参数
iterator = createIterator();
console.log(iterator.next());//{ value: 'welcome', done: false }
console.log(iterator.next('hello')); //{ value: 'helloworld', done: false }
console.log(iterator.next('work')); //{ value: 'workhard', done: false }
console.log(iterator.next());//{ value: undefined, done: true }
这里有个特例,第一次调用next()方法时无论传入什么参数都会被丢弃。由于传给next()方法的参数会代替上一次yield的返回值,而在第一次调用next()方法前不会执行任何yield语句,因此在第一次调用next()方法时传递参数是毫无意义的。
2.在迭代器中抛出错误
通过throw()方法当迭代器恢复执行时可令其抛出一个错误。这种主动抛出错误的能力对于异步编程而言至关重要,也能为你提供模拟结束函数执行的两种方法(返回值或抛出错误),从而增强生成器内部的编程弹性。将错误对象抛给throw()方法后,在迭代器继续时其会被抛出。
因此,我们可以在生成器内部通过try-catch代码块来捕获这些错误:
function* createIterator() {
let first = yield 1;
let second;
try {
second = yield first + 2;
} catch (ex) {
second = 6;
}
yield second + 3;
}
let iterator = createIterator();
console.log(iterator.next()); //{ value: 1, done: false }
console.log(iterator.next(4)); //{ value: 6, done: false }
console.log(iterator.throw(new Error("Boom"))); //{ value: 9, done: false }
console.log(iterator.next()); //{ value: undefined, done: true }
next()和throw()就像是迭代器的两条指令,调用next()方法命令迭代器继续执行(可能提供一个值),调用throw()方法也会命令迭代器继续执行,但同时会抛出一个错误,在此之后的执行过程取决于生成器内部的代码。
3.生成器返回语句
在生成器中,return表示所有操作已经完成,属性done被设置为true;如果同时提供了相应的值,则属性value会被设置为这个值。
function* createIterator() {
yield 1;
return 12;
}
let iterator = createIterator();
console.log(iterator.next()); //{ value: 1, done: false }
console.log(iterator.next()); //{ value: 12, done: true }
console.log(iterator.next()); //{ value: undefined, done: true }
上面例子可以得知:通过return语句指定的返回值,只会在返回对象中出现一次,在后续调用返回的对象中,value属性会被重置为undefined;
注意: 展开运算符与for-of循环语句会直接忽略通过return语句指定的任何返回值,只要done一变为true就立即停止读取其他的值。
4.委托生成器
如果在 Generator 函数内部,调用另一个 Generator 函数(这就叫委托生成器),默认情况下是没有效果的。这个就需要用到yield*表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数。
function* createNumberIterator() {
yield 1;
yield 2;
}
function* createColorIterator() {
yield 'red';
yield 'green';
}
function* createCombinedIterator() {
yield* createNumberIterator();
yield* createColorIterator();
yield true;
}
let iterator = createCombinedIterator();
console.log(iterator.next()); //{ value: 1, done: false }
console.log(iterator.next()); //{ value: 2, done: false }
console.log(iterator.next()); //{ value: 'red', done: false }
console.log(iterator.next()); //{ value: 'green', done: false }
console.log(iterator.next()); //{ value: true, done: false }
console.log(iterator.next()); //{ value: undefined, done: true }
上面的例子中createNumberIterator函数没有返回值,下面来看下有返回值的Gentertor函数:
function* createNumberIterator() {
yield 1;
yield 2;
return 3;
}
function* createRepeatingIterator(count) {
for (let i = 0; i < count; i++) {
yield "repeat";
}
}
function* createCombinedIterator() {
let result = yield* createNumberIterator();
yield* createRepeatingIterator(result);
}
let iterator = createCombinedIterator();
console.log(iterator.next()); //{ value: 1, done: false }
console.log(iterator.next()); //{ value: 2, done: false }
console.log(iterator.next()); //{ value: 'repeat', done: false }
console.log(iterator.next()); //{ value: 'repeat', done: false }
console.log(iterator.next()); //{ value: 'repeat', done: false }
console.log(iterator.next()); //{ value: undefined, done: true }
注意: 上面的代码无论通过何种方式调用迭代器的next()方法,数值3永远不会被返回,它只存在于生成器createCombinedIterator()的内部。但如果想输出这个值,则可以额外添加一条yield语句。
yield result;
yield*也可直接应用于字符串,例如yield * “hello”, 此时将使用字符串的默认迭代器。
5.同步任务执行器
由于执行yield语句会暂停当前函数的执行过程并等待下一次调用next()方法,因此你可以创建一个函数,在函数中调用生成器生成相应的迭代器。
function run(taskDef) {
//创建一个无使用限制的迭代器
let task = taskDef();
//开始执行任务
let result = task.next();
//循环调用next()的函数
function step() {
//如果任务未完成,则继续执行
if (!result.done) {
result = task.next(result.value);
step();
}
}
step();
}
run(function* (){
let value = yield 1;
console.log(value); // 1
value = yield value +3;
console.log(value); // 4
});
6.异步任务执行器
以Node.js的异步任务为例
let fs = require('fs');
function run(taskDef) {
//创建一个无使用限制的迭代器
let task = taskDef();
//开始执行任务
let result = task.next();
//循环调用next()的函数
function step() {
//如果任务未完成,则继续执行
if (!result.done) {
//这里假设如果result.value是一个函数,那么就一定是异步执行的。
if (typeof result.value === 'function') {
//传入一个回调函数作为参数来调用它,回调函数遵循Node.js中有关
//错误的约定:所有可能的错误放在第一个参数err中,结果放在第二个参数中。
result.value(function (err, data) {
//err存在,说明执行过程产生了错误
if (err) {
result = task.throw(err);
}
//如果没有错误产生,data就会被复制给yield左边的变量
result = task.next(data);
step();
})
} else {
result = task.next(result.value);
step();
}
}
}
step();
}
//readFile必须返回一个函数,接收callback
function readFile(filename) {
return function (callback) {
fs.readFile(filename, callback);
}
}
run(function* () {
let contents = yield readFile("txt.text");
console.log(contents); //类似这样的结果 <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>
});
以上内容,整理自《深入理解ES6》一书