原文地址: Understanding Generators in ES6 JavaScript with Examples
简介
es6 新增了生成器函数,以便更好地使用函数和迭代器。generator 是一个函数,函数可以在执行的过程中停止,记住该状态,下次继续执行的时候从该状态开始。简单来说,generator是一个有迭代器功能的函数。
async/await 是generator的语法题。
generator 跟 iterator 密切相关。
举一个简单的例子来理解generator: 假如你正在看一本书,突然门铃响了,是你叫的外卖到了,这个时候你会把书签夹在你正在阅读的页面,然后合上书本,起身开门。吃完外卖你重新打开书本,可以从有标签的那页开始读起,而不用从第一页开始读。这大概就是generator的思想。
什么是generator
下面这个函数是从头开始执行直到结束的,想让它停止执行的唯一方法是return或者抛出错误,重新开始时则又再次重头执行:
function normalFunc() {
console.log('I')
console.log('cannot')
console.log('be')
console.log('stopped.')
}
generator 则可以随时停止执行,再次开始时从原来停止的位置开始。generator函数返回一个对象,该对象可以调用next()方法,每次调用会返回value和done。generator函数有可能永远都不会结束。
创建一个generator
function * generatorFunction() { // Line 1
console.log('This will be executed first.');
yield 'Hello, '; // Line 2
console.log('I will be printed after the pause');
yield 'World!';
}
const generatorObject = generatorFunction(); // Line 3
console.log(generatorObject.next().value); // Line 4
console.log(generatorObject.next().value); // Line 5
console.log(generatorObject.next().value); // Line 6
// This will be executed first.
// Hello,
// I will be printed after the pause
// World!
// undefined
line 3 创建了一个generator对象,generator对象是一个迭代器。
line 4 generator函数开始执行,遇到yield停下来
line 5 generator继续执行,从上一个yield结束开始
line 6 generator继续执行,但此时已经没有代码可以执行了,因此返回{value: undefined, done: true}
function, *, 函数名之间可以有无数给空格。因为generator本质是一个函数,所有可以创建和调用函数的位置,相应的也可以创建和调用generator。
generator没有使用return语句,使用的是yield,每当程序执行到yield语句的时候,就会返回yield语句后面跟着的值。
generator也可以使用return语句,但return语句后面的yield语句会失效:
function * generatorFunc() {
yield 'a';
return 'b'; // Generator ends here.
yield 'a'; // Will never be executed.
}
generator实践
- 实现可迭代
要让一个对象可迭代,我们需要自己实现next方法,还需要保存当前状态,实现一个类似闭包的函数:
const iterableObj = {
[Symbol.iterator]() {
let step = 0;
return {
next() {
step++;
if (step === 1) {
return { value: 'This', done: false};
} else if (step === 2) {
return { value: 'is', done: false};
} else if (step === 3) {
return { value: 'iterable.', done: false};
}
return { value: '', done: true };
}
}
},
}
for (const val of iterableObj) {
console.log(val);
}
// This
// is
// iterable.
使用generator同样可以实现上述功能:
function * iterableObj() {
yield 'This';
yield 'is';
yield 'iterable.'
}
for (const val of iterableObj()) {
console.log(val);
}
// This
// is
// iterable.
- 异步代码
执行异步代码可以通过Promise, callback等:
function fetchJson(url) {
return fetch(url)
.then(request => request.text())
.then(text => {
return JSON.parse(text);
})
.catch(error => {
console.log(`ERROR: ${error.stack}`);
});
}
也可以使用generator(借助 co.js):
const fetchJson = co.wrap(function * (url) {
try {
let request = yield fetch(url);
let text = yield request.text();
return JSON.parse(text);
}
catch (error) {
console.log(`ERROR: ${error.stack}`);
}
});
- 无限数据流
我们可以创建一个永远不会结束的generator.如下代码,因为while 条件一直为true,代码可以随时开始执行随时停止。
function * naturalNumbers() {
let num = 1;
while (true) {
yield num;
num = num + 1
}
}
const numbers = naturalNumbers();
console.log(numbers.next().value)
console.log(numbers.next().value)
// 1
// 2
- 观察者模式
generator可以通过next(val)函数来传参。这时generator会被称为观察者,因为收到值后generator会继续执行。
generator优点
- 延迟计算
思考一下我们上面提到的无限数据流,如果是在普通函数下,那么while里面的语句会不停的执行,页面会出现卡顿,内存会被快速消耗。但是在generator语境下,我们只在需要的时候才执行while里面的语句,所以不会存在普通函数产生的问题。
- 节约内存
延迟计算的直接结果就是节约内存。
注意事项
- generator对象只提供一次性访问,但返回{value: undefined, done: true}之后,无法再重头开始遍历,需要重新创建一个generator对象
- generator对象不像数组一样可以任意访问某一个值,generator访问到具体某一个值的时候必定已经访问到该值之前的所有值,所以不能说是随机访问。
generator与协程
协程是一种程序运行的方式,可以用单线程实现,ES6对协程的实现是通过generator。