跳到主要内容

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);