内容来自 > ES6+ Generator
生成器对象和生成器函数
-
生成器对象 是遵守迭代协议和迭代器协议实现的 Iterable 接口,可以理解 生成器对象 其实也是一个迭代器;
-
生成器函数 是由 function * 来定义的,并且返回结果是一个 Generator 对象;
-
生成器可以使用 yield 关键字来暂停执行的生成器函数;
function* generator() { yield 'a'; yield 'b'; } var gen = generator(); console.log(gen); // Object [Generator] {}
Generator.prototype.next()
-
生成器的 next() 方法和迭代器返回的结果是一样的,返回了一个包含属性 done 和 value 的对象,该方法也可以通过接受一个参数用以向生成器传值;
-
使用 yield 返回的值会被迭代器的 next() 方法捕获;
function* generator() { yield 'a'; yield 'b'; } // 生成器函数在执行后会返回一个生成器对象,这个生成器对象满足迭代协议和迭代器协议 var gen = generator(); // 手动调用它的 next () 方法去获取每一步的返回值 gen.next(); // {value: 'a', done: false} gen.next(); // {value: 'b', done: false} gen.next(); // {value: undefined, done: true}
Generator.prototype.return()
-
return() 方法返回给定的值并结束生成器;
function* generator() { yield 'a'; yield 'b'; } var gen = generator(); gen.next(); // { value: 'a', done: false } gen.return("hello"); // { value: "hello", done: true } gen.next(); // { value: undefined, done: true }
-
如果对已经完成状态的生成器调用 return(value) 则生成器会一直保持在完成状态,如果传入参数,value 会设置成传入的参数,done 的值不变;
function* generator() { yield 'a'; yield 'b'; } var gen = generator(); gen.next(); // { value: 'a', done: false } gen.next(); // { value: 'b', done: false } gen.next(); // { value: undefined, done: true } gen.return(); // { value: undefined, done: true } gen.return(1); // { value: 1, done: true }
Generator.prototype.throw()
-
throw() 方法用来向生成器抛出异常,并恢复生成器的执行,返回带有 done 及 value 两个属性的对象;
function* generator() { while(true) { try { yield 'hello' } catch(e) { console.log("Error caught!"); } } } var gen = generator(); gen.next(); // { value: "hello", done: false } gen.throw(new Error("error")); // "Error caught!",{value: 'hello', done: false}
Generator 案例
类数组转化
const likeArr = { 0: 1, 1: 2, length: 2 };
// 方式一:实现迭代器(手动实现 next 函数)
likeArr[Symbol.iterator] = function () {
let index = 0;
return {
next: () => ({ value: this[index], done: index++ === this.length });
}
}
console.log([...likeArr]); // [1, 2]
const likeArr = { 0: 1, 1: 2, length: 2 };
// 方式二:生成器实现(不需要手动实现 next () 方法,生成器函数更加简单方便)
likeArr[Symbol.iterator] = function* () {
let index = 0;
while (index !== this.length) {
yield this[index++];
}
}
console.log([...likeArr]); // [1, 2]
单步获取质数
题目:实现一个函数,每次调用返回下一个质数,要求不使用全局变量,且函数本身不接受任何参数
// 方式一:闭包方法来解决
function primeHandler() {
let prime = 1;
return () => {
while (true) {
if (isPrime(++prime)) return prime;
}
}
}
function isPrime(num) {
for (let i = 2; i <= Math.sqrt(num); i++) {
if (num % i === 0) return false;
}
return true;
}
const getPrime = primeHandler();
console.log(getPrime()); // 2
console.log(getPrime()); // 3
console.log(getPrime()); // 5
// 方式二:迭代器方式实现
var prime = {
[Symbol.iterator]() {
let prime = 1;
return {
next() {
while (true) {
if (isPrime(++prime)) return prime;
}
}
};
}
};
function isPrime(num) {
for (let i = 2; i <= Math.sqrt(num); i++) {
if (num % i === 0) return false;
}
return true;
}
var getPrime = prime[Symbol.iterator]().next;
console.log(getPrime()); // 2
console.log(getPrime()); // 3
console.log(getPrime()); // 5
// 方式三:生成器函数实现
function* primeGenerator() {
let prime = 1;
while (true) {
if (isPrime(++prime)) yield prime;
}
}
function isPrime(num) {
for (let i = 2; i <= Math.sqrt(num); i++) {
if (num % i === 0) return false;
}
return true;
}
var getPrime = primeGenerator();
console.log(getPrime.next().value); // 2
console.log(getPrime.next().value); // 3
console.log(getPrime.next().value); // 5
Generator 函数应用
Generator 函数在异步中的应用,解决了某些场景下还会产生回调地狱的问题,通过封装 co 方法让代码写起来像是同步一样;
Generator 函数还不是解决异步的终极方案,后面会学习 async 函数 (async + await 相当于 generator + co),看它是怎么来解决异步的;
回调地狱问题
如果想要获取第二个文件中的内容,但是文件名未知,需要读取 name.txt 之后才知道这个文件名字;
如果使用了两层 promsie 嵌套,则代码的书写方式并不友好,若需求是 n 个文件岂不是需要嵌套 n 层?
Generator 初步解决
// 第一个文件:name.txt 的内容 age.txt
// 第二个文件:age.txt 的内容 10
const fs = require('fs').promises;
// 代码编写更像是同步的,更友好的编码方式 (执行还是异步的)
function* read() {
try {
let name = yield fs.readFile('name.txt', 'utf8');
let age = yield fs.readFile(name, 'utf8');
return age;
} catch (error) {
console.log(error);
}
}
let it = read(); // 获取生成器对象
let { value, done } = it.next(); // value 是个 promise
// 从下面的代码中看出还是有嵌套,细心观察发现每个回调的逻辑基本都是一样的,可以使用 co 库来解决这个问题
value.then(data => { // 每个回调的逻辑基本都是一样
let { value, done } = it.next(data);
console.log(value, done);
value.then(data => { // 每个回调的逻辑基本都是一样
let { value, done } = it.next(data);
console.log(value, done);
})
})
co 库的内部原理
// name.txt 的内容 age.txt
// age.txt 的内容 10
const fs = require('fs').promises;
// 代码编写更像是同步的,更友好的编码方式 (执行还是异步的)
function* read() {
try {
let name = yield fs.readFile('name.txt', 'utf8');
let age = yield fs.readFile(name, 'utf8');
return age;
} catch (error) {
console.log(error);
}
}
// 手写 co,异步迭代:co 有现成的库,koa、express 的作者 tj
const co = it => new Promise((resolve, reject) => {
// 异步迭代的核心是 => 回调函数
function next(data) {
let { value, done } = it.next(data);
if (!done) {
// 默认成功后会调用 next 方法,将结果传递到 next 函数中
// 如果 yield 返回的不是一个 Promise 对象时,我们对 value 使用了 Promise.resolve() 进行了包装,这样就可以处理返回一个普通值时没有 then 方法的问题
Promise.resolve(value).then(next, reject);
} else {
resolve(value);
}
}
next();
});
// 测试:co 会将生成器的结果转成 promise
co(read())
.then(data => console.log(data))
.catch(err => console.log(err))
第 4️⃣ 座大山:Promise
上一篇