什么是同步迭代器呢?
举个例子:
var obj = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
}
var iterator = obj[Symbol.iterator]()
iterator.next() // {value: 1, done: false}
iterator.next() // {value: 2, done: false}
iterator.next() // {value: 3, done: false}
iterator.next() // {value: undefined, done: true}
这里的 iterator
就是同步迭代器了,每调用一次 next
方法,就返回一个 { value: xx, done: xx }
形式的对象。
什么是异步迭代器呢?
再举个例子:
var obj = {
async *[Symbol.asyncIterator]() {
yield 1;
yield 2;
yield 3;
}
}
var 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}
与同步可迭代对象部署了 [Symbol.iterator]
属性不同的是,异步可迭代对象的标志是部署了 [Symbol.asyncIterator]
这个属性。
这里的 asyncIterator
就是异步迭代器了。与同步迭代器 iterator
不同的是,在 asyncIterator
上调用 next
方法得到是一个 Promise 对象,其内部值是 { value: xx, done: xx }
的形式,类似于 Promise.resolve({ value: xx, done: xx })
。
为什么会有异步迭代器呢?
同步迭代器里数据都是当时就能获取的(没有延迟),而异步迭代器里的数据往往获取是需要时间的(有延迟)。
下面举了几个例子,来说明为什么要用异步迭代器,以及它的使用场景。
- 同步迭代器数据即用即给,处理顺序等于遍历顺序。
var obj = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
}
for (let item of obj) {
console.log(item) // 1 -> 2 -> 3。处理顺序等于遍历顺序
}
- 如果同步迭代器数据获取需要时间,那么再用
for-of
遍历的话,就有问题。
var 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
可以把这里的每个 item
当成是接口请求,数据返回的时间不一定的。上面的打印结果就说明了问题所在——我们控制不了数据的处理顺序。
- 再来看看异步迭代器
var obj = {
async *[Symbol.asyncIterator]() {
yield new Promise(resolve => setTimeout(() => resolve(1), 5000));
yield new Promise(resolve => setTimeout(() => resolve(2), 3000));
yield new Promise(resolve => setTimeout(() => resolve(3), 500));
}
}
console.log(Date.now())
for await (let item of obj) {
console.log(Date.now(), item)
}
// 1579256590699
// 1579256595700 1 // 1579256595700 - 1579256590699 = 5001
// 1579256598702 2 // 1579256598702 - 1579256590699 = 8003
// 1579256599203 3 // 1579256599203 - 1579256590699 = 8504
注意,异步迭代器要声明在 [Symbol.asyncIterator]
属性里,使用 for-await-of 循环处理的。最终效果是,对任务挨个处理,上一个任务等待处理完毕后,再进入下一个任务。
因此,异步迭代器就是用来处理这种不能即时拿到数据的情况,还能保证最终的处理顺序等于遍历顺序,不过需要依次排队等待。
for-await-of
for-await-of 除了能用在异步可迭代对象上,还能用在同步可迭代对象上。
var obj = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
}
for await(let item of obj) {
console.log(item) // 1 -> 2 -> 3
}
是这样的,如果 for-await-of 发现遍历对象上没有 [Symbol.asyncIterator]
的话,就去调用 [Symbol.iterator]
。我们都知道,由此得来的是同步迭代器,在这个迭代器上调用 next
方法的时候,返回就是一个普通的对象,结构类似于 { value: xx, done: xx }
。
而 await obj
的结果还是 obj
,下例为证:
(async function() {
var obj = { value: 1, done: false }
console.log((await obj) === obj) // true
})()
所以才会有上面的结果。
还有一点,需要注意的是,如果一个对象上同时部署了 [Symbol.asyncIterator]
和 [Symbol.iterator]
,那就会优先使用 [Symbol.asyncIterator]
生成的异步迭代器。这很好理解,因为 for-await-of 本来就是为异步迭代器而生的。
var obj = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
},
async *[Symbol.asyncIterator]() {
yield 4;
yield 5;
yield 6;
}
}
for await(let item of obj) {
console.log(item) // 4 -> 5 -> 6。优先使用由 [Symbol.asyncIterator] 生成的异步迭代器
}