for await...of
Baseline Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since January 2020.
尝试一下
语法
描述
当 for await...of
循环迭代一个可迭代对象时,它首先获取可迭代对象的 [Symbol.asyncIterator]()
方法并调用它,该方法返回一个异步迭代器。如果 @asyncIterator
方法不存在,则会查找 [Symbol.iterator]()
方法,该方法返回一个同步迭代器。然后将返回的同步迭代器封装成一个异步迭代器,通过将 next()
、return()
和 throw()
方法返回的每个对象都包装成一个已兑现或已拒绝的 promise,如果 value
属性也是一个 promise,则将其兑现。然后循环重复调用最终异步迭代器的 next()
方法,并等待(await)返回的 promise,以生成要分配给 variable
的值的序列。
如果 for await...of
循环提前退出(例如遇到 break
语句或抛出错误),则会调用迭代器的 return()
方法来执行任何清理任务。在循环退出之前,会等待返回的 promise。
for await...of
的功能与 for...of
循环基本上相同,并且共享许多相同的语法和语义。但也有一些区别:
for await...of
可以用于同步和异步可迭代对象,而for...of
仅适用于同步可迭代对象。for await...of
只能在可以使用await
的上下文中使用,包括在异步函数体内和模块中使用。即使可迭代对象是同步的,循环仍会等待每次迭代的返回值,导致执行速度较慢,因为需要重复解包 promise。- 如果
iterable
是一个产生 promise 的同步可迭代对象,for await...of
会生成一个已兑现的值序列,而for...of
会生成一个 promise 序列。(然而,需要注意错误处理和清理——请参阅迭代同步可迭代对象和生成器) - 对于
for await...of
,variable
可以是标识符async
(例如for await (async of foo)
);for...of
不允许这种情况。
示例
迭代异步可迭代对象
你还可以迭代一个显式实现异步可迭代协议的对象:
const LIMIT = 3;
const asyncIterable = {
[Symbol.asyncIterator]() {
let i = 0;
return {
next() {
const done = i === LIMIT;
const value = done ? undefined : i++;
return Promise.resolve({ value, done });
},
return() {
// 如果消费者在循环中提前调用“break”或“return”,则会运行到这里。
return { done: true };
},
};
},
};
(async () => {
for await (const num of asyncIterable) {
console.log(num);
}
})();
// 0
// 1
// 2
迭代异步生成器
由于异步生成器函数的返回值符合异步可迭代协议,因此可以使用 for await...of
来迭代它们。
async function* asyncGenerator() {
let i = 0;
while (i < 3) {
yield i++;
}
}
(async () => {
for await (const num of asyncGenerator()) {
console.log(num);
}
})();
// 0
// 1
// 2
如果需要使用 for await...of
迭代异步生成器的更具体示例时,可以考虑迭代来自 API 的数据。
该示例首先创建一个用于数据流的异步可迭代对象,然后使用它来查找 API 响应的大小。
async function* streamAsyncIterable(stream) {
const reader = stream.getReader();
try {
while (true) {
const { done, value } = await reader.read();
if (done) return;
yield value;
}
} finally {
reader.releaseLock();
}
}
// 从 URL 获取数据并使用异步生成器计算响应的大小。
async function getResponseSize(url) {
const response = await fetch(url);
// 将保存响应的大小,以字节为单位。
let responseSize = 0;
// for-await-of 循环。异步迭代器遍历响应的每个部分。
for await (const chunk of streamAsyncIterable(response.body)) {
// 增加响应总长度。
responseSize += chunk.length;
}
console.log(`响应大小:${responseSize} 字节`); // "响应大小:1071472 字节"
return responseSize;
}
getResponseSize("https://jsonplaceholder.typicode.com/photos");
迭代同步可迭代对象和生成器
for await...of
循环还可以处理同步可迭代对象和生成器。在这种情况下,它会在将值分配给循环控制变量之前,在内部对其进行等待。
function* generator() {
yield 0;
yield 1;
yield Promise.resolve(2);
yield Promise.resolve(3);
yield 4;
}
(async () => {
for await (const num of generator()) {
console.log(num);
}
})();
// 0
// 1
// 2
// 3
// 4
// 与 for-of 循环比较:
for (const numOrPromise of generator()) {
console.log(numOrPromise);
}
// 0
// 1
// Promise { 2 }
// Promise { 3 }
// 4
备注:
请注意,如果同步生成器生成了一个被拒绝的 promise,那么在使用 for await...of
进行处理时会抛出异常,并且不会调用生成器内部的 finally
块。如果你需要使用 try/finally
来释放一些已分配的资源,这可能是不可取的。
function* generatorWithRejectedPromises() {
try {
yield 0;
yield 1;
yield Promise.resolve(2);
yield Promise.reject(3);
yield 4;
throw 5;
} finally {
console.log("调用了 finally");
}
}
(async () => {
try {
for await (const num of generatorWithRejectedPromises()) {
console.log(num);
}
} catch (e) {
console.log("捕获了异常", e);
}
})();
// 0
// 1
// 2
// 捕获了异常 3
// 与 for-of 循环比较:
try {
for (const numOrPromise of generatorWithRejectedPromises()) {
console.log(numOrPromise);
}
} catch (e) {
console.log("捕获了异常", e);
}
// 0
// 1
// Promise { 2 }
// Promise { <rejected> 3 }
// 4
// 捕获了异常 5
// 调用了 finally
为了确保同步生成器的 finally
块始终被调用,需要使用对应的循环形式——对于异步生成器使用 for await...of
,对于同步生成器则使用 for...of
,并在循环内显式地等待生成的 promise。
(async () => {
try {
for (const numOrPromise of generatorWithRejectedPromises()) {
console.log(await numOrPromise);
}
} catch (e) {
console.log("捕获了异常", e);
}
})();
// 0
// 1
// 2
// 捕获了异常 3
// 调用了 finally
规范
Specification |
---|
ECMAScript Language Specification # sec-for-in-and-for-of-statements |
浏览器兼容性
BCD tables only load in the browser