1. 基础概念
1.1 什么是 await
?
- 定义:
await
是 JavaScript 中的关键字,用于处理异步操作,只能在async
函数内部使用。 - 作用:暂停当前函数的执行,直到一个 Promise 完成(resolve 或 reject),并返回 Promise 的结果。
- 核心特性:
- 使异步代码看起来像同步代码,提高可读性。
- 自动处理 Promise 的成功(resolve)和失败(reject)状态。
1.2 基本语法
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:', error);
}
}
// 调用异步函数
fetchData();
2. 工作原理
2.1 await
与 Promise 的关系
- 底层依赖:
await
是基于 Promise 的语法糖,内部通过 Promise 的链式调用实现。 - 执行流程:
await
表达式会暂停当前async
函数的执行。- 等待 Promise 完成(resolve 或 reject)。
- 如果 Promise resolve,返回结果;如果 reject,抛出错误(需用
try...catch
捕获)。
- 等价转换:
// 使用 await 的代码 async function fetchData() { const response = await fetch(url); const data = await response.json(); return data; } // 等价于 Promise 链式调用 function fetchData() { return fetch(url) .then(response => response.json()) .then(data => data); }
2.2 async
函数的作用
- 声明方式:通过
async
关键字声明函数,使其返回一个 Promise 对象。async function myFunction() { return 'Hello'; } // 等价于 function myFunction() { return Promise.resolve('Hello'); }
- 错误处理:
async
函数内部未捕获的错误会传递给返回的 Promise 的reject
。
3. 核心使用场景
3.1 顺序执行异步操作
- 场景:需要按顺序执行多个异步操作时,
await
使代码逻辑更清晰。async function processUser(userId) { const user = await User.findById(userId); const posts = await Post.find({ userId }); return { user, posts }; }
3.2 并行执行异步操作
- 场景:多个独立的异步操作可以并行执行,使用
Promise.all
提升性能。async function fetchAllData() { const [data1, data2] = await Promise.all([ fetchData1(), fetchData2(), ]); console.log(data1, data2); }
3.3 错误处理
- 使用
try...catch
:捕获await
抛出的错误。async function fetchData() { try { const response = await fetch(url); const data = await response.json(); return data; } catch (error) { console.error('Fetch failed:', error); throw error; // 可选:将错误传递给上层 } }
3.4 结合 Promise.all
和 Promise.race
Promise.all
:等待所有 Promise 完成。Promise.race
:等待第一个完成的 Promise。async function raceExample() { const first = await Promise.race([ fetch(url1), fetch(url2), ]); console.log('First response:', first); }
4. 最佳实践
4.1 避免不必要的 await
- 错误示例:在非异步操作前使用
await
。// 错误:await 用于非 Promise 值 async function example() { const value = await 42; // 42 会被包装成 Promise,但无意义 console.log(value); }
4.2 合理使用并行
- 性能优化:并行执行独立任务,减少总耗时。
async function parallelExample() { const [data1, data2] = await Promise.all([ fetchData('url1'), fetchData('url2'), ]); console.log(data1, data2); }
4.3 错误处理的最佳实践
- 集中处理错误:在顶层统一处理错误,避免重复代码。
async function main() { try { const data = await fetchData(); console.log(data); } catch (error) { console.error('Global error handler:', error); } }
4.4 避免阻塞事件循环
- 注意:
await
仅暂停当前async
函数,不会阻塞 Node.js 事件循环。async function nonBlocking() { console.log('Start'); await new Promise(resolve => setTimeout(resolve, 2000)); console.log('End'); } nonBlocking(); console.log('This runs immediately'); // 输出在 "Start" 之后,"End" 之前
5. 常见错误与解决方案
5.1 在非 async
函数中使用 await
- 错误:语法错误,
await
必须位于async
函数内。// 错误示例 function incorrectUsage() { const result = await fetchData(); // SyntaxError: Unexpected identifier } // 正确示例 async function correctUsage() { const result = await fetchData(); }
5.2 未处理 Promise 拒绝(reject)
- 错误:未捕获的 Promise 拒绝会导致未处理错误。
async function unsafeExample() { const response = await fetch('https://invalid-url'); // 可能抛出错误 // 未使用 try...catch 或 .catch() } // 正确做法 async function safeExample() { try { const response = await fetch('https://invalid-url'); } catch (error) { console.error('Error handled:', error); } }
5.3 循环中的不当使用
- 错误:在循环中顺序执行
await
,导致性能下降。// 错误:顺序执行 async function sequentialLoop() { for (const url of urls) { const data = await fetch(url); // 每次循环等待上一个完成 } } // 正确:并行执行 async function parallelLoop() { const promises = urls.map(url => fetch(url)); const results = await Promise.all(promises); }
5.4 响应重复问题
- 场景:在 Web 服务器中,多个请求导致响应重复发送。
- 解决方案:使用标志变量确保响应只发送一次。
let responseSent = false; app.get('/endpoint', async (req, res) => { if (responseSent) return; responseSent = true; try { const data = await someAsyncOperation(); res.send(data); } catch (error) { res.status(500).send('Error'); } });
6. 高级技巧
6.1 顶层 await
(ES Module)
- 支持环境:在 ES Module(
.mjs
或"type": "module"
)中,可直接在顶层使用await
。// ES Module 文件 const data = await fetchData(); console.log(data);
6.2 自定义异步工具函数
- 示例:封装通用的异步操作。
async function withRetry(fn, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await fn(); } catch (error) { if (i === maxRetries - 1) throw error; } } } // 使用 const data = await withRetry(() => fetchData());
6.3 结合 AsyncLocalStorage
- 场景:在异步操作中传递上下文(如日志、请求 ID)。
const { AsyncLocalStorage } = require('async_hooks'); const asyncLocalStorage = new AsyncLocalStorage(); async function processRequest(req) { return asyncLocalStorage.run(req.id, async () => { const data = await fetchData(); console.log('Request ID:', asyncLocalStorage.getStore()); return data; }); }
7. 总结
- 核心价值:
await
简化了异步编程,使代码更直观、易维护。 - 关键点:
- 只能在
async
函数中使用。 - 结合
try...catch
处理错误。 - 合理使用
Promise.all
提升性能。
- 只能在
- 最佳实践:
- 避免不必要的
await
。 - 并行执行独立任务。
- 集中处理错误,确保代码健壮性。
- 避免不必要的
通过深入理解 await
的工作原理和最佳实践,您可以更高效地编写 Node.js 异步代码,提升应用性能和可维护性。