乐闻世界logo
搜索文章和话题

async/await 是如何工作的?与 Promise 有什么关系?

2月22日 14:31

async/await 是 ES2017 引入的语法糖,用于处理异步操作,它基于 Promise 构建,让异步代码看起来更像同步代码,大大提高了代码的可读性和可维护性。

async 函数

基本概念

async 函数是使用 async 关键字声明的函数,它总是返回一个 Promise。即使函数内部没有显式返回 Promise,也会被包装成一个 Promise。

基本用法

javascript
async function fetchData() { return 'Hello World'; } // 等同于 function fetchData() { return Promise.resolve('Hello World'); } fetchData().then(result => console.log(result)); // 输出: Hello World

返回 Promise

javascript
async 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 的结果。

基本用法

javascript
async function fetchData() { const promise = Promise.resolve('Hello'); const result = await promise; console.log(result); // 输出: Hello return result; } fetchData();

等待多个 Promise

javascript
async 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

javascript
async function fetchData() { try { const response = await fetch('/api/data'); const data = await response.json(); return data; } catch (error) { console.error('请求失败:', error.message); throw error; // 可以选择重新抛出错误 } }

捕获特定错误

javascript
async 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() 链式调用

javascript
function 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(更易读)

javascript
async 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

javascript
async 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

javascript
async 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. 顺序执行异步操作

javascript
async function processItems(items) { const results = []; for (const item of items) { const result = await processItem(item); results.push(result); } return results; }

2. 并行执行异步操作

javascript
async function processItemsParallel(items) { const promises = items.map(item => processItem(item)); const results = await Promise.all(promises); return results; }

3. 带超时的请求

javascript
async 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. 重试机制

javascript
async 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 进行清理

javascript
async 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()); }

总结

  1. async 函数总是返回 Promise:即使返回普通值也会被包装成 Promise
  2. await 暂停执行:等待 Promise 完成后继续执行
  3. 使用 try/catch 处理错误:确保错误被正确捕获和处理
  4. 并行执行提高性能:使用 Promise.all 并行执行独立的异步操作
  5. 避免过度嵌套:保持代码扁平和清晰
  6. 理解与 Promise 的关系:async/await 是 Promise 的语法糖,可以互相转换
标签:Promise