跳到主要内容

Async/Await

基本概念​

什么是 async 函数?​

async 函数是一个返回 Promise 对象的函数。在函数内部,可以使用 await 关键字来等待一个 Promise 的完成。这使得异步代码看起来更像同步代码,提升了代码的可读性和维护性。

什么是 await 关键字?​

await 关键字只能在 async 函数内部使用。它会暂停当前函数的执行,直到对应的 Promise 完成,并返回结果。这允许开发者以更直观的方式处理异步操作。

async 函数返回什么类型的值?​

async 函数总是返回一个 Promise。无论函数内部返回的是一个值还是抛出一个错误,最终都会被封装在一个 Promise 对象中。

async function example() {
return 42;
}

example().then((value) => console.log(value)); // 输出: 42

错误处理​

如何在使用 await 时处理错误?​

在使用 await 时,可以通过 try/catch 结构来捕获和处理错误。这确保了异步操作中的异常能够被适当地处理,避免程序崩溃。

async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('数据获取失败', error);
}
}

try/catch 在 async/await 中的作用是什么?​

try/catch 用于捕获 await 表达式中的错误或在 async 函数内部抛出的错误。这使得错误处理更加直观和集中。

如何处理多个 await 调用中的并发错误?​

如果同时等待多个 Promise,可以使用 Promise.all() 来并发处理。当其中一个 Promise 失败时,Promise.all() 会立即失败。此时,可以使用 try/catch 来捕获错误。

async function fetchMultipleData() {
try {
const [data1, data2] = await Promise.all([fetch('https://api.example.com/data1'), fetch('https://api.example.com/data2')]);
const result1 = await data1.json();
const result2 = await data2.json();
console.log(result1, result2);
} catch (error) {
console.error('其中一个数据获取失败', error);
}
}

并发性​

如何并发地执行多个 await 操作?​

可以使用 Promise.all() 或 Promise.allSettled() 来并发执行多个 Promise。这些方法允许多个异步操作同时进行,提高了执行效率。

async function concurrentOperations() {
const results = await Promise.all([fetch('https://api.example.com/data1'), fetch('https://api.example.com/data2'), fetch('https://api.example.com/data3')]);
const data = await Promise.all(results.map((res) => res.json()));
console.log(data);
}

Promise.all() 和 Promise.race() 在与 async/await 结合时有什么作用?​

Promise.all() 允许并发等待多个 Promise,返回一个在所有 Promise 完成后完成的 Promise。如果其中一个 Promise 失败,整个操作会失败。Promise.race() 返回一个在第一个 Promise 完成后完成的 Promise,无论其成功还是失败。

async function exampleAll() {
try {
const results = await Promise.all([fetch('https://api.example.com/data1'), fetch('https://api.example.com/data2')]);
// 处理结果
} catch (error) {
console.error('其中一个请求失败', error);
}
}

async function exampleRace() {
const result = await Promise.race([fetch('https://api.example.com/data1'), fetch('https://api.example.com/data2')]);
// 处理第一个完成的结果
}

与 Promise 的关系​

async/await 和 Promises 之间有什么关系?​

async/await 是基于 Promise 的语法糖,提供了一种更简洁和直观的方式来处理异步操作。它们共同简化了异步代码的编写和维护。

在 async 函数中返回一个值等价于哪种 Promise 操作?​

在 async 函数中返回一个值等价于使用 Promise.resolve(),而抛出一个错误则等价于使用 Promise.reject()。

async function returnValue() {
return 42;
}

returnValue().then((value) => console.log(value)); // 相当于 Promise.resolve(42)

async function throwError() {
throw new Error('发生错误');
}

throwError().catch((error) => console.error(error)); // 相当于 Promise.reject(new Error('发生错误'))

执行顺序​

描述一个包含多个 await 语句的 async 函数的执行顺序。​

async 函数从顶部开始执行,遇到第一个 await 时暂停,等待对应的 Promise 完成后继续执行。这个过程会依次进行,直到函数结束或遇到下一个 await。

async function executionOrder() {
console.log('开始');
const result1 = await asyncOperation1();
console.log('完成第一个操作');
const result2 = await asyncOperation2();
console.log('完成第二个操作');
}

何时使用 await 和何时避免使用?​

当需要按顺序执行异步操作时,使用 await 可以使代码更具可读性。当可以并发执行多个操作时,使用 Promise.all() 等方法可以提高效率。

错误和陷阱​

描述在使用 async/await 时可能遇到的常见错误或陷阱。​

常见错误包括忘记使用 await,在非 async 函数内部使用 await,在循环中不正确地使用 await 导致不必要的顺序执行,以及未处理的异步错误。

如何避免“忘记 await”的问题?​

可以使用 TypeScript 或 ESLint 等工具,这些工具能够检测出遗漏的 await,帮助开发者提前发现并修复问题。

// 使用 ESLint 的规则可以帮助捕捉此类错误
async function example() {
const data = fetch('https://api.example.com/data'); // 忘记了 await
console.log(data);
}

实际应用​

在实际开发中,常常需要将回调或 Promise 形式的代码转换为使用 async/await。这不仅提升了代码的可读性,还简化了错误处理。

// 使用 Promise
function fetchData() {
return fetch('https://api.example.com/data')
.then((response) => response.json())
.then((data) => {
console.log(data);
})
.catch((error) => {
console.error('错误', error);
});
}

// 使用 async/await
async function fetchDataAsync() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('错误', error);
}
}

浏览器和 Node.js 支持​

哪些版本的 Node.js 和浏览器开始支持 async/await?​

Node.js 从 7.6.0 版本开始支持 async/await。大多数现代浏览器,如最新版本的 Chrome、Firefox、Edge 和 Safari,都已支持 async/await。然而,旧版本的浏览器可能不支持,需要使用转译工具。

在不支持 async/await 的环境中,如何使用它?​

可以使用 Babel 或 TypeScript 等转译器,将 async/await 转换为 ES5 或 ES6 兼容的 JavaScript 代码,从而在不支持的环境中使用。

# 使用 Babel 转译
babel src --out-dir lib

性能和最佳实践​

async/await 是否比纯 Promises 更慢?​

在大多数情况下,async/await 和纯 Promises 的性能差异可以忽略。然而,async/await 可能会带来一些额外的运行时开销,尤其是在大量使用时。

何时应该使用 async/await,何时应该使用 Promises?​

当需要编写更简洁和可读的代码时,使用 async/await 是更好的选择。对于复杂的异步逻辑或需要更细粒度控制的场景,使用 Promises 可能更为合适。

其他特性和提议​

对于 async iterators 和 for-await-of 的了解。​

async iterators 允许异步地生成值,结合 for-await-of 循环,可以逐步处理异步数据流。这在处理大规模数据或实时数据流时非常有用。

async function* asyncGenerator() {
yield '数据1';
yield '数据2';
yield '数据3';
}

(async () => {
for await (const data of asyncGenerator()) {
console.log(data);
}
})();

了解其他与 async/await 相关的 ES 提议,如顶级 await。​

顶级 await 是一个提议,允许在模块的顶层作用域中使用 await,而无需将代码封装在 async 函数中。这简化了模块初始化时的异步操作。

// 顶级 await 示例
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);