async/await 是 ES2017 引入的语法糖,用于处理异步操作,它基于 Promise 构建,让异步代码看起来更像同步代码,大大提高了代码的可读性和可维护性。
async 函数
基本概念
async 函数是使用 async 关键字声明的函数,它总是返回一个 Promise。即使函数内部没有显式返回 Promise,也会被包装成一个 Promise。
基本用法
javascriptasync function fetchData() { return 'Hello World'; } // 等同于 function fetchData() { return Promise.resolve('Hello World'); } fetchData().then(result => console.log(result)); // 输出: Hello World
返回 Promise
javascriptasync function fetchData() { // 返回普通值 return 42; } async function fetchDataWithError() { // 抛出错误 throw new Error('出错了'); } fetchData().then(result => console.log(result)); // 输出: 42 fetchDataWithError().catch(error => console.error(error.message)); // 输出: 出错了
await 表达式
基本概念
await 关键字只能在 async 函数内部使用,它会暂停 async 函数的执行,等待 Promise 完成,然后返回 Promise 的结果。
基本用法
javascriptasync function fetchData() { const promise = Promise.resolve('Hello'); const result = await promise; console.log(result); // 输出: Hello return result; } fetchData();
等待多个 Promise
javascriptasync function fetchMultipleData() { const promise1 = fetch('/api/user'); const promise2 = fetch('/api/posts'); const [userResponse, postsResponse] = await Promise.all([promise1, promise2]); const user = await userResponse.json(); const posts = await postsResponse.json(); return { user, posts }; }
错误处理
使用 try/catch
javascriptasync function fetchData() { try { const response = await fetch('/api/data'); const data = await response.json(); return data; } catch (error) { console.error('请求失败:', error.message); throw error; // 可以选择重新抛出错误 } }
捕获特定错误
javascriptasync function fetchData() { try { const response = await fetch('/api/data'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { if (error.name === 'TypeError') { console.error('网络连接问题'); } else if (error.message.includes('HTTP error')) { console.error('服务器错误'); } else { console.error('未知错误:', error); } throw error; } }
async/await vs Promise.then()
Promise.then() 链式调用
javascriptfunction fetchData() { return fetch('/api/user') .then(response => response.json()) .then(user => fetch(`/api/posts/${user.id}`)) .then(response => response.json()) .then(posts => ({ user, posts })) .catch(error => { console.error(error); throw error; }); }
async/await(更易读)
javascriptasync function fetchData() { try { const userResponse = await fetch('/api/user'); const user = await userResponse.json(); const postsResponse = await fetch(`/api/posts/${user.id}`); const posts = await postsResponse.json(); return { user, posts }; } catch (error) { console.error(error); throw error; } }
并行执行
使用 Promise.all
javascriptasync function fetchAllData() { const [user, posts, comments] = await Promise.all([ fetch('/api/user').then(r => r.json()), fetch('/api/posts').then(r => r.json()), fetch('/api/comments').then(r => r.json()) ]); return { user, posts, comments }; }
使用 Promise.allSettled
javascriptasync function fetchAllDataWithErrors() { const results = await Promise.allSettled([ fetch('/api/user').then(r => r.json()), fetch('/api/posts').then(r => r.json()), fetch('/api/comments').then(r => r.json()) ]); results.forEach((result, index) => { if (result.status === 'fulfilled') { console.log(`请求 ${index} 成功:`, result.value); } else { console.error(`请求 ${index} 失败:`, result.reason); } }); return results; }
常见使用场景
1. 顺序执行异步操作
javascriptasync function processItems(items) { const results = []; for (const item of items) { const result = await processItem(item); results.push(result); } return results; }
2. 并行执行异步操作
javascriptasync function processItemsParallel(items) { const promises = items.map(item => processItem(item)); const results = await Promise.all(promises); return results; }
3. 带超时的请求
javascriptasync function fetchWithTimeout(url, timeout = 5000) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); try { const response = await fetch(url, { signal: controller.signal }); clearTimeout(timeoutId); return await response.json(); } catch (error) { clearTimeout(timeoutId); if (error.name === 'AbortError') { throw new Error('请求超时'); } throw error; } }
4. 重试机制
javascriptasync function fetchWithRetry(url, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.error(`尝试 ${i + 1} 失败:`, error.message); if (i === maxRetries - 1) { throw error; } await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); } } }
最佳实践
1. 总是使用 try/catch
javascript// 不推荐:没有错误处理 async function fetchData() { const response = await fetch('/api/data'); return await response.json(); } // 推荐:添加错误处理 async function fetchData() { try { const response = await fetch('/api/data'); return await response.json(); } catch (error) { console.error('请求失败:', error); throw error; } }
2. 避免在循环中顺序 await
javascript// 不推荐:顺序执行,速度慢 async function processItems(items) { const results = []; for (const item of items) { const result = await processItem(item); results.push(result); } return results; } // 推荐:并行执行,速度快 async function processItems(items) { const promises = items.map(item => processItem(item)); return await Promise.all(promises); }
3. 合理使用 Promise.all
javascript// 推荐:并行执行独立的异步操作 async function fetchData() { const [user, posts, comments] = await Promise.all([ fetchUser(), fetchPosts(), fetchComments() ]); return { user, posts, comments }; }
4. 使用 finally 进行清理
javascriptasync function fetchData() { let connection; try { connection = await createConnection(); const data = await connection.query('SELECT * FROM users'); return data; } catch (error) { console.error('查询失败:', error); throw error; } finally { if (connection) { await connection.close(); } } }
常见陷阱
1. 忘记使用 await
javascript// 错误:没有 await async function fetchData() { const promise = fetch('/api/data'); // promise 是一个 Promise 对象,不是数据 console.log(promise); // 输出: Promise {<pending>} } // 正确:使用 await async function fetchData() { const response = await fetch('/api/data'); const data = await response.json(); console.log(data); }
2. 在非 async 函数中使用 await
javascript// 错误:在非 async 函数中使用 await function fetchData() { const data = await fetch('/api/data'); // SyntaxError } // 正确:在 async 函数中使用 await async function fetchData() { const data = await fetch('/api/data'); return data; }
3. 过度使用 try/catch
javascript// 不推荐:过度使用 try/catch async function fetchData() { try { try { const response = await fetch('/api/data'); try { const data = await response.json(); return data; } catch (error) { console.error('解析失败:', error); } } catch (error) { console.error('请求失败:', error); } } catch (error) { console.error('未知错误:', error); } } // 推荐:合理使用 try/catch async function fetchData() { try { const response = await fetch('/api/data'); return await response.json(); } catch (error) { console.error('请求或解析失败:', error); throw error; } }
与 Promise 的关系
async/await 本质上是 Promise 的语法糖,它们之间可以互相转换:
javascript// async/await async function fetchData() { const response = await fetch('/api/data'); return await response.json(); } // 等同于 Promise function fetchData() { return fetch('/api/data') .then(response => response.json()); }
总结
- async 函数总是返回 Promise:即使返回普通值也会被包装成 Promise
- await 暂停执行:等待 Promise 完成后继续执行
- 使用 try/catch 处理错误:确保错误被正确捕获和处理
- 并行执行提高性能:使用 Promise.all 并行执行独立的异步操作
- 避免过度嵌套:保持代码扁平和清晰
- 理解与 Promise 的关系:async/await 是 Promise 的语法糖,可以互相转换