为什么要有异步迭代器

同步迭代器里数据都是当时就能获取的(没有延迟),而异步迭代器里的数据往往获取是需要时间的(有延迟)。

如果同步迭代器数据获取需要时间(比如实际场景中请求接口),那么再用 for-of 遍历的话,就有问题——控制不了数据的处理顺序

1
2
3
4
5
6
7
8
9
10
11
let obj = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
}

for (let item of obj) {
console.log(item) // 1 -> 2 -> 3。处理顺序等于遍历顺序
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 如果同步迭代器数据获取需要时间,那么再用 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 用生成器生成
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}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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

1
2
3
4
5
6
7
8
9
10
11
12
// 迭代器
interface Iterator {
next(value) : IteratorResult;
[optional] throw(value) : IteratorResult;
[optional] return(value) : IteratorResult;
}

// 迭代结果
interface IteratorResult {
value : any;
done : bool;
}

Async Iterators

1
2
3
4
5
6
7
8
9
10
11
12
// 异步迭代器
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 语句除了能用在异步可迭代对象上,还能用在同步可迭代对象上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
1
2
3
4
5
6
7
8
9
10
11
12
const obj = {
*[Symbol.iterator]() {
yield 1
yield 2
yield 3
}
};
(async () => {
for await(const item of obj) {
console.log(item) // 1 -> 2 -> 3
}
})()

注意是顺序问题:

  1. 如果一个对象上同时部署了 [Symbol.asyncIterator] [Symbol.iterator] ,那就会优先使用 [Symbol.asyncIterator] 生成的异步迭代器。这很好理解,因为 for-await-of 本来就是为异步迭代器而生的
  2. 相反如果同时部署了两个迭代器,但使用的是for-of那么优先使用同步迭代器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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* 的行为以支持异步迭代
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 异步生成器函数,返回一个异步迭代器对象
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