我知道有很多资源可以解释
for await...of
,但我认为我永远不会完全掌握这个概念,直到我看到一个以完全相同的方式工作但具有更基本语法的示例。
事实是,如果我的理解是正确的,这两个
for
循环的行为应该完全相同:
for(const p of iterableWithPromises) {
const q = await p;
// ...
}
for await (const q of iterableWithPromises) {
// ...
}
是这样吗?这种语法只是让我们免于在循环内创建一个新变量来存储 Promise 的结果吗?
不,这两个循环并不完全相同。
首先要做的事情:粗略地说,这就是传统
for ...of
循环与 普通迭代器 的等效形式(为简洁起见,省略了循环中使用break、continue、异常和返回等极端情况):
// Given this iterable...
const iterable = {
[Symbol.iterator]() {
console.log('[Symbol.iterator]() called');
let i = 0;
return {
next() {
console.log('next() called');
const iteratorResult = { value: i, done: i > 3 };
i++;
return iteratorResult;
},
};
},
};
// ...this for loop...
for (const value of iterable) {
console.log(`VALUE: ${value}`);
}
// ...is equivalent to this while loop:
const iterator = iterable[Symbol.iterator]();
let iteratorResult = iterator.next();
while(!iteratorResult.done){
const value = iteratorResult.value;
console.log(`VALUE: ${value}`);
iteratorResult = iterator.next();
}
// And this would be the output:
//
// [Symbol.iterator]() called
// next() called
// VALUE: 0
// next() called
// VALUE: 1
// next() called
// VALUE: 2
// next() called
// VALUE: 3
// next() called
现在,使用
for await...of
和 async iterables,等价物将是这样的:
const makePromise = (name, seconds, value) => {
console.log(`Creating a promise named ${name} that will resolve in ${seconds} seconds with a value of ${JSON.stringify(value)}...`);
return new Promise((resolve) => {
console.log(`Promise ${name} created`);
setTimeout(() => {
console.log(`Resolving promise ${name}...`);
resolve(value);
console.log(`Promise ${name} resolved`);
}, seconds*1000)
});
}
// Given this iterable...
const iterable = {
[Symbol.asyncIterator]() {
console.log('[Symbol.asyncIterator]() called');
let i = 0;
return {
next() {
console.log('next() called');
const iteratorResult = makePromise(`promise-${i}`, i, { value: i, done: i > 3 });
i++;
return iteratorResult;
},
};
},
};
// ...this for loop...
for await (const value of iterable) {
console.log(`VALUE: ${value}`);
}
// ...is equivalent to this while loop:
const iterator = iterable[Symbol.asyncIterator]();
let iteratorResult = await iterator.next();
while(!iteratorResult.done){
const value = iteratorResult.value;
console.log(`VALUE: ${value}`);
iteratorResult = await iterator.next();
}
// And this would be the output:
//
// [Symbol.asyncIterator]() called
// next() called
// Creating a promise named promise-0 that will resolve in 0 seconds with a value of {"value":0,"done":false}...
// Promise promise-0 created
// (0 seconds later...)
// Resolving promise promise-0...
// Promise promise-0 resolved
// VALUE: 0
// next() called
// Creating a promise named promise-1 that will resolve in 1 seconds with a value of {"value":1,"done":false}...
// Promise promise-1 created
// (1 second later...)
// Resolving promise promise-1...
// Promise promise-1 resolved
// VALUE: 1
// next() called
// Creating a promise named promise-2 that will resolve in 2 seconds with a value of {"value":2,"done":false}...
// Promise promise-2 created
// (2 seconds later...)
// Resolving promise promise-2...
// Promise promise-2 resolved
// VALUE: 2
// next() called
// Creating a promise named promise-3 that will resolve in 3 seconds with a value of {"value":3,"done":false}...
// Promise promise-3 created
// (3 seconds later...)
// Resolving promise promise-3...
// Promise promise-3 resolved
// VALUE: 3
// next() called
// Creating a promise named promise-4 that will resolve in 4 seconds with a value of {"value":4,"done":true}...
// Promise promise-4 created
// (4 seconds later...)
// Resolving promise promise-4...
// Promise promise-4 resolved
现在让我们说我最初关于这种 for 循环如何工作的错误假设是正确的。然后我们应该能够用普通的可迭代替换异步可迭代,如下所示:
const iterable = {
[Symbol.iterator]() {
console.log('[Symbol.iterator]() called');
let i = 0;
return {
next() {
console.log('next() called');
const iteratorResult = {
value: makePromise(`promise-${i}`, i, i),
done: i > 3
};
i++;
return iteratorResult;
},
};
},
};
如果您使用最后一个迭代运行任何示例,您将发现结果没有任何差异。即使在一次输出和下一次输出之间的时间里也不行。但有一点你应该注意:当使用
done
时,next()
返回的对象的 for await...of
属性包含在 Promise 中。这在决定 for 循环是否应该停止迭代取决于 Promise 的结果的情况下是相关的。
例如,假设您有一个 REST api,它在响应 json 对象的一个字段中包含一个 url,用于继续获取下一个结果:从技术上讲,您仍然可以使用普通迭代器来实现此目的,但有一些注意事项:
next()
时,您必须确保 done
评估为 true
,无论 REST api 是否实际上有任何数据,以确保至少发出第一个请求,否则首先不会发出任何请求,并且您没有任何方法来判断是否有任何数据(如果计算结果为 false,则请求仍将完成,但循环将结束而没有任何机会对数据做任何事情,并且您将无法对此做任何事情)。done
属性的值时才会停止。时间。您可以通过在实现异步迭代器的服务器中获取数据来防止这种情况发生,就像我在示例中所做的那样(注意在 for await...of
示例中我如何使用 [Symbol.asyncIterator]
而不是 [Symbol.iterator]
)来强制开发人员使用 for await...of
和预防这些问题。当然。您是正确的,这两个循环具有相似的目的:它们都迭代包含 Promise 的可迭代对象,并允许您处理它们的解析值。但是,
for await...of
提供了更清晰、更简洁的语法,专门设计用于处理异步迭代,而您的第一个示例是使用常规 for
循环实现相同结果的更详细方法。
为什么要使用
for await...of
?
for
await...of
循环在处理异步迭代时特别有用,异步迭代会随着时间的推移提供值,例如产生承诺的流或生成器。
示例:使用异步生成器
async function* asyncGenerator() {
yield Promise.resolve(1);
yield Promise.resolve(2);
yield Promise.resolve(3);
}
(async () => {
for await (const value of asyncGenerator()) {
console.log(value); // Logs 1, then 2, then 3
}
})();
这不能用
for...of
循环轻松复制,因为 for...of
本身并不理解异步迭代。
如果您想获得引擎盖,请选择 sepc。