之前我们在Symbol
一章中介绍了iterator
迭代器,今天主要来看看generator
生成器。
回顾一下什么是迭代器:一种有序、连续的基于抽取的消耗数据的组织方式。
而对象是无序的,所以不存在迭代器,我们可以给object
部署一个迭代器:
function iterator() {
let array = Object.entries(this);
let nextIndex = 0;
return {
next() {
return nextIndex < array.length
? { value: array[nextIndex++], done: false }
: { value: undefined, done: true };
},
};
}
let obj = {
a: 1,
b: 2,
c: 3,
[Symbol.iterator]: iterator
};
for (const iterator of obj) {
console.log(iterator);
}
// ['a', 1]
// ['b', 2]
// ['c', 3]
在JS
中默认调用迭代器接口的场合有:
for...of...
遍历...
拓展运算符Array.from()
Map
和Set
初始化数据Promise.all()
和Promise.race()
yield
生成器
生成器generator
函数写法也是一个函数,只不过多了一个*
,函数的返回值是一个迭代器对象
function* test() {}
let iter = test();
console.log(iter); // test {<suspended>}
console.log(iter.next()); // {value: undefined, done: true}
generator
函数要和yield
关键词配合使用才能凸显出它的作用。yield
简单理解就是产出的意思,每次yield
就相当于给迭代器产出一个值,当函数运行的时候遇到yield
会暂停函数的执行。
function* test() {
yield "a";
console.log(1); // 遇到 yield 会暂停函数的执行,所以这里不会执行
yield "b";
yield "c";
return "d";
}
let iter = test();
因为generator
返回的是迭代器对象,所以可以通过调用next
方法来取得yield
产出的值。
function* test() {
yield "a";
console.log(1);
yield "b";
yield "c";
return "d";
}
let iter = test();
console.log(iter.next()); // {value: 'a', done: false}
console.log(iter.next()); // 1 (第二次调用 next 方法的时候才会打印 1)
// {value: 'b', done: false}
yield
的值可以被接收,但是接收的方式却让人费解!yield
的值是通过next
传递的参数来决定的,而不是yield
本身。
function* foo() {
let value1 = yield 1;
console.log(value1); // two
let value2 = yield 2;
console.log(value2); // three
let value3 = yield 3;
console.log(value3); // four
let value4 = yield 4;
console.log(value4); // five
}
let iter = foo();
console.log(iter.next("one")); // 这里的 one 没有用
console.log(iter.next("two"));
console.log(iter.next("three"));
console.log(iter.next("four"));
console.log(iter.next("five"));
这有点交叉传值的感觉:
yield
和return
的区别:
return
依然会产出值,但是done
为true
,再次next
的时候值为undefinde
yield
本意是产出暂停,具有记忆功能,继上次next
后可以继续next
⚠️ 注意yield
只能出现在生成器函数中,否则会抛异常
function test() {
yield "a"; // Unexpected string
}
let iter = test();
console.log(iter)
yield
也可以当作表达式来使用:
function* demo() {
yield; // yield 单独存在
console.log("hello" + (yield 123)); // 表达式中使用需要使用括号
}
let iter = demo();
console.log(iter.next());
// yield 当做参数传递的时候,无法获取值
// 先执行两次 yield 然后才执行 foo()
function foo(a, b) {
console.log(a, b); // undefined undefined
}
function* demo() {
foo(yield "a", yield "b");
}
let iter = demo();
console.log(iter.next()); // {value: 'a', done: false}
console.log(iter.next()); // {value: 'b', done: false}
console.log(iter.next()); // {value: undefined, done: true}
因为generator
返回的是迭代器对象,所以可以利用for...of...
来遍历:
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (const iterator of foo()) {
console.log(iterator); // 1 2 3 4 5 ,不包括 return 的 6
}
到此我们对yield
有了一个基本的认识,我们可以简化一下文章开头时模拟的object
迭代器接口:
let obj = {
start: [1, 2, 3, 4],
end: [7, 8, 9, 10],
[Symbol.iterator]: function* () {
let nextIndex = 0;
let array = this.start.concat(this.end);
// 不需要再判断 return 值
while (nextIndex < array.length) {
yield array[nextIndex++];
}
},
};
for (const iterator of obj) {
console.log(iterator);
}
迭代器的案例
在异步概念之前假如我们想实现连续的读取文件需要一直使用回调,这样很容易造成回调地狱:
let fs = require("fs");
fs.readFile("./name.txt", "utf-8", (err, data) => {
fs.readFile(data, "utf-8", (err, data) => {
fs.readFile(data, "utf-8", (err, data) => {
console.log(data);
});
});
});
当有了promise
后我们可以将fs.readFile
函数封装位异步操作:
function promisify(fn) {
return function (...args) {
return new Promise((resolve, reject) => {
fn(...args, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
};
}
let fs = require("fs");
let readFile = promisify(fs.readFile);
readFile("./name.txt", "utf-8")
.then((res) => readFile(res))
.then((res) => console.log(res));
如何利用yield
来封装呢?
let fs = require("fs");
let readFile = promisify(fs.readFile);
function promisify(fn) {
return function (...args) {
return new Promise((resolve, reject) => {
fn(...args, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
};
}
function* read() {
let value1 = yield readFile("./name.txt", "utf-8");
let value2 = yield readFile(value1, "utf-8");
let value3 = yield readFile(value2, "utf-8");
console.log(value3);
}
// 不如 .then 的写法简单
let iter = read();
let { value } = iter.next(); // 返回迭代器对象,将 value 进行解构
value.then((result) => {
let { value } = iter.next(result);
value.then((result) => {
let { value } = iter.next(result);
value.then((result) => {
console.log(result);
});
});
});
但是这样的写法还不如promisify
的写法简单,那我们继续优化:
let fs = require("fs");
let readFile = promisify(fs.readFile);
function promisify(fn) {
return function (...args) {
return new Promise((resolve, reject) => {
fn(...args, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
};
}
function* read() {
let value1 = yield readFile("./name.txt", "utf-8");
let value2 = yield readFile(value1, "utf-8");
let value3 = yield readFile(value2, "utf-8");
console.log(value3);
}
function Co(iter) {
return new Promise((resolve, reject) => {
let next = (data) => {
let { value, done } = iter.next(data);
if (done) {
resolve(value);
} else {
value.then((res) => next(res));
}
};
next();
});
}
let promise = Co(read());
promise.then((result) => {
console.log(result);
});
而这样的写法就是async
、await
的演变过程(*
表示async
,yield
表示await
)