异步迭代该怎么搞?异步迭代器,该你上场了!(⁎⁍̴̛ᴗ⁍̴̛⁎)
为什么要有异步迭代器
同步迭代器里数据都是当时就能获取的(没有延迟),而异步迭代器里的数据往往获取是需要时间的(有延迟)。
如果同步迭代器数据获取需要时间(比如实际场景中请求接口),那么再用
for-of遍历的话,就有问题——控制不了数据的处理顺序。
let obj = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
}
for (let item of obj) {
console.log(item) // 1 -> 2 -> 3。处理顺序等于遍历顺序
}
// 如果同步迭代器数据获取需要时间,那么再用 for-of 遍历的话,就有问题。
const obj = {
*[Symbol.iterator]() {
yield new Promise(resolve => setTimeout(() => resolve(1), 5000));
yield new Promise(resolve => setTimeout(() => resolve(2), 2000));
yield new Promise(resolve => setTimeout(() => resolve(3), 500));
}
}
console.log(Date.now())
for (let item of obj) {
item.then(data => console.log(Date.now(), data))
}
// 1579253648926
// 1579253649427 3 // 1579253649427 - 1579253648926 = 501
// 1579253650927 2 // 1579253650927 - 1579253648926 = 2001
// 1579253653927 1 // 1579253653927 - 1579253648926 = 5001
let obj = {
async *[Symbol.asyncIterator]() {
yield new Promise(resolve => setTimeout(() => resolve(1), 2000));
yield new Promise(resolve => setTimeout(() => resolve(2), 1000));
yield new Promise(resolve => setTimeout(() => resolve(3), 500));
}
}
async function test(){
console.log(Date.now())
for await (let item of obj) {
console.log(Date.now(), item)
}
}
test()
// 1715239216352
// 1715239218363 1
// 1715239219370 2
// 1715239219883 3
注意,异步迭代器要声明在 [Symbol.asyncIterator] 属性里,使用 for-await-of 循环处理的。 最终效果是,对任务挨个处理,上一个任务等待处理完毕后,再进入下一个任务。
异步迭代器
与同步可迭代对象部署了
[Symbol.iterator]属性不同的是,异步可迭代对象的标志是部署了[Symbol.asyncIterator]这个属性。
异步迭代器用来处理不能即时拿到数据的情况,还能保证最终的处理顺序等于遍历顺序,不过需要依次排队等待。
与同步迭代器 iterator 不同的是,在 asyncIterator 上调用 next 方法得到是一个 Promise 对象,其内部值是 { value: xx, done: xx } 的形式,类似于 Promise.resolve({ value: xx, done: xx }) 。
// 用生成器生成
const obj = {
async *[Symbol.asyncIterator]() {
yield 1;
yield 2;
yield 3;
}
}
const asyncIterator = obj[Symbol.asyncIterator]()
asyncIterator.next().then(data => console.log(data)) // {value: 1, done: false}
asyncIterator.next().then(data => console.log(data)) // {value: 2, done: false}
asyncIterator.next().then(data => console.log(data)) // {value: 3, done: false}
asyncIterator.next().then(data => console.log(data)) // {value: undefined, done: true}
const justjavac = {
[Symbol.asyncIterator]: () => {
const items = [`j`, `u`, `s`, `t`, `j`, `a`, `v`, `a`, `c`];
return {
next: () => Promise.resolve({
done: items.length === 0,
value: items.shift()
})
}
}
}
;(async function(){
for await (const item of justjavac) {
console.log(item)
}
})();
// 输出:
// j
// u
// s
// t
// j
// a
// v
// a
// c
同步迭代器vs异步迭代器
Iterator
// 迭代器
interface Iterator {
next(value) : IteratorResult;
[optional] throw(value) : IteratorResult;
[optional] return(value) : IteratorResult;
}
// 迭代结果
interface IteratorResult {
value : any;
done : bool;
}
Async Iterators
// 异步迭代器
interface AsyncIterator {
next(value) : Promise<IteratorResult>;
[optional] throw(value) : Promise<IteratorResult>;
[optional] return(value) : Promise<IteratorResult>;
}
// 迭代结果
interface IteratorResult {
value : any;
done : bool;
}
异步迭代语句
for await...of语句创建一个循环,该循环遍历 异步可迭代对象 以及 同步可迭代对象。该语句只能在可以使用await的上下文中使用 ,包括异步函数体内以及模块中。 也就是说for-await-of语句除了能用在异步可迭代对象上,还能用在同步可迭代对象上。
async function* asyncGenerator() {
yield new Promise(resolve => setTimeout(() => resolve('First'), 3000));
yield new Promise(resolve => setTimeout(() => resolve('Second'), 2000));
yield new Promise(resolve => setTimeout(() => resolve('Third'), 1000));
}
async function test() {
for await (let result of asyncGenerator()) {
console.log(Date.now(), Date.now() - pre, result);
}
}
const pre = Date.now();
test();
// 1715239395981 3006 First
// 1715239397996 5021 Second
// 1715239398998 6023 Third
const obj = {
*[Symbol.iterator]() {
yield 1
yield 2
yield 3
}
};
(async () => {
for await(const item of obj) {
console.log(item) // 1 -> 2 -> 3
}
})()
注意是顺序问题:
- 如果一个对象上同时部署了
[Symbol.asyncIterator]和[Symbol.iterator],那就会优先使用[Symbol.asyncIterator]生成的异步迭代器。这很好理解,因为for-await-of本来就是为异步迭代器而生的。 - 相反如果同时部署了两个迭代器,但使用的是
for-of那么优先使用同步迭代器。
const obj = {
*[Symbol.iterator]() {
yield 1
yield 2
yield 3
},
async *[Symbol.asyncIterator]() {
yield 4
yield 5
yield 6
}
}
// 异步
;(async () => {
for await(const item of obj) {
console.log(item) // 4 -> 5 -> 6。优先使用由 [Symbol.asyncIterator] 生成的异步迭代器
}
})()
// 同步
for (const item of obj) {
console.log(item) // 1 -> 2 -> 3。优先使用由 [Symbol.iterator] 生成的同步迭代器
}
// 输出:(先执行同步再执行异步)
// 1
// 2
// 3
// 4
// 5
// 6
异步生成器函数
异步生成器函数与生成器函数类似,但有以下区别:
- 当被调用时,异步生成器函数返回一个对象,"
async generator",含有 3 个方法(next,throw,和return),每个方法都返回一个 Promise,Promise 返回{ value, done }。而普通生成器函数并不返回 Promise,而是直接返回{ value, done }。 这会自动使返回的异步生成器对象具有异步迭代的功能。 - 允许使用
await表达式和for-await-of语句。 - 修改了
yield*的行为以支持异步迭代。
// 异步生成器函数,返回一个异步迭代器对象
async function* asyncGenerator() {
let count = 3;
while (count >= 1) {
// 模拟异步操作,每次迭代都等待不同的时间
let delay = count * 1000;
let result = await new Promise(resolve => setTimeout(() => resolve(`Result ${count}`), delay));
yield result;
count--;
}
}
// 异步迭代器生成函数的使用示例
async function processData() {
// 使用 for-await-of 语句遍历异步生成器返回的结果
for await (let result of asyncGenerator()) {
console.log(Date.now(), Date.now() - pre, result);
}
}
const pre = Date.now();
// 调用异步迭代器生成函数
processData();
// 1715240686112 3017 Result 3
// 1715240688125 5030 Result 2
// 1715240689131 6036 Result 1