pin_drop当前位置:知识文库 ❯ 图文
后端通信:async/await - 从入门到实践详解
教程简介
async/await是ES2017引入的异步编程语法糖,让异步代码看起来和同步代码一样直观。它是基于Promise的语法增强,将Promise的链式调用转换为更易读的顺序结构。async/await已成为现代JavaScript异步编程的首选方式,在前端网络请求、文件操作、定时器等场景中广泛使用。
本教程将全面讲解async函数、await表达式、错误处理、并行执行、循环中的async/await、顶层await、与Promise的互操作,以及常见陷阱。
核心概念
1. async函数
async关键字声明一个异步函数,函数总是返回一个Promise:
代码示例
// async函数声明
async function fetchUser(id) {
return { id, name: '张三' }; // 自动包装为Promise.resolve()
}
// 等价于
function fetchUser(id) {
return Promise.resolve({ id, name: '张三' });
}
// async函数表达式
const fetchUser = async (id) => {
return { id, name: '张三' };
};
// async方法
const api = {
async getUser(id) {
return { id, name: '张三' };
}
};
// async箭头函数
const delay = async (ms) => new Promise(resolve => setTimeout(resolve, ms));async函数的返回值处理:
2. await表达式
await只能在async函数内使用(或作为顶层await),它会暂停async函数的执行,等待Promise解决:
代码示例
async function getUserOrders(userId) {
// 暂停执行,等待Promise解决
const user = await fetchUser(userId);
console.log(user.name);
const orders = await fetchOrders(user.id);
console.log(orders);
return orders;
}await的行为:
如果Promise fulfilled,返回解决的值
如果Promise rejected,抛出拒绝的原因
如果等到的不是Promise,直接返回该值
代码示例
async function demo() {
const a = await 42; // 42
const b = await Promise.resolve('hello'); // 'hello'
const c = await null; // null
}3. 错误处理(try/catch)
代码示例
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
// 捕获网络错误、HTTP错误、JSON解析错误
console.error('获取用户数据失败:', error.message);
throw error; // 可以选择重新抛出
}
}try/catch vs .catch():
代码示例
// try/catch方式
async function way1() {
try {
const data = await fetchData();
return processData(data);
} catch (error) {
handleError(error);
}
}
// .catch()方式
async function way2() {
return fetchData()
.then(data => processData(data))
.catch(error => handleError(error));
}多个await的错误处理策略:
代码示例
// 策略1:一个try/catch包裹所有
async function strategy1() {
try {
const user = await fetchUser();
const orders = await fetchOrders();
const products = await fetchProducts();
} catch (error) {
console.error('任一请求失败:', error);
}
}
// 策略2:每个await单独try/catch
async function strategy2() {
let user, orders, products;
try { user = await fetchUser(); } catch (e) { console.error('用户获取失败'); }
try { orders = await fetchOrders(); } catch (e) { console.error('订单获取失败'); }
try { products = await fetchProducts(); } catch (e) { console.error('产品获取失败'); }
}
// 策略3:使用辅助函数
async function strategy3() {
const [user, orders, products] = await Promise.all([
fetchUser().catch(e => (console.error(e), null)),
fetchOrders().catch(e => (console.error(e), null)),
fetchProducts().catch(e => (console.error(e), null))
]);
}4. 并行执行
代码示例
// 串行执行(慢)- 总时间 = 所有请求时间之和
async function serial() {
const users = await fetchUsers(); // 1秒
const orders = await fetchOrders(); // 1秒
const products = await fetchProducts(); // 1秒
// 总共约3秒
}
// 并行执行(快)- 总时间 = 最慢的请求时间
async function parallel() {
const [users, orders, products] = await Promise.all([
fetchUsers(),
fetchOrders(),
fetchProducts()
]);
// 总共约1秒
}
// 部分并行
async function partialParallel() {
// 先获取用户,再并行获取订单和产品
const user = await fetchUser(userId);
const [orders, products] = await Promise.all([
fetchOrders(user.id),
fetchProducts(user.id)
]);
}5. 循环中的async/await
代码示例
// 串行执行(每次等待上一个完成)
async function sequentialLoop(items) {
for (const item of items) {
await processItem(item); // 等待每个完成后再处理下一个
}
}
// 并行执行(同时发起所有请求)
async function parallelLoop(items) {
await Promise.all(items.map(item => processItem(item)));
}
// 限制并发的循环
async function limitedConcurrentLoop(items, limit = 3) {
const executing = new Set();
for (const item of items) {
const p = processItem(item).then(() => executing.delete(p));
executing.add(p);
if (executing.size >= limit) {
await Promise.race(executing);
}
}
await Promise.all(executing);
}
// for...of循环(串行)
async function forOfLoop(urls) {
const results = [];
for (const url of urls) {
const res = await fetch(url);
results.push(await res.json());
}
return results;
}
// forEach的陷阱 - 不会等待async回调
async function forEachTrap(urls) {
const results = [];
// 错误!forEach不会等待async回调完成
urls.forEach(async (url) => {
const res = await fetch(url);
results.push(await res.json());
});
return results; // 可能是空数组!
}6. 顶层await
ES2022允许在模块顶层使用await,无需包裹在async函数中:
代码示例
// module.mjs
const config = await fetch('/api/config').then(r => r.json());
const db = await connectDatabase(config.dbUrl);
export { config, db };注意:顶层await只在ES模块中可用,且会阻塞当前模块的执行。
7. 与Promise的互操作
代码示例
// async函数返回的Promise可以用.then/.catch处理
async function getData() {
return { name: '张三' };
}
getData().then(data => console.log(data)).catch(error => console.error(error));
// Promise可以用await等待
const data = await Promise.resolve({ name: '张三' });
// async函数可以await任何thenable
const thenable = {
then(resolve) {
resolve('thenable值');
}
};
const value = await thenable; // 'thenable值'8. 常见陷阱
陷阱1:忘记await
代码示例
async function trap1() {
// 忘记await,得到Promise对象而非值
const data = fetchData(); // 错误!应该是 await fetchData()
console.log(data); // Promise {<pending>}
}陷阱2:forEach中使用async
代码示例
// 不会等待所有异步操作完成
[1, 2, 3].forEach(async (n) => {
await delay(1000);
console.log(n);
});
console.log('done'); // 先输出'done',再输出1,2,3陷阱3:不必要的串行
代码示例
// 不必要的串行
async function unnecessarySerial() {
const a = await getValueA(); // 等待A
const b = await getValueB(); // 再等待B
// A和B没有依赖关系,应该并行
}
// 正确做法
async function correctParallel() {
const [a, b] = await Promise.all([getValueA(), getValueB()]);
}陷阱4:try/catch吞掉错误
代码示例
async function swallowError() {
try {
await riskyOperation();
} catch (error) {
// 只记录不重新抛出,调用者不知道出错了
console.error(error);
}
}代码示例
示例1:async/await交互演示
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>async/await交互演示</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', system-ui, sans-serif; background: #f0f2f5; padding: 20px; color: #333; }
.container { max-width: 900px; margin: 0 auto; }
h1 { text-align: center; color: #1a73e8; margin-bottom: 24px; font-size: 28px; }
.card { background: #fff; border-radius: 12px; padding: 24px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.card h2 { font-size: 18px; color: #1a73e8; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 2px solid #e8eaed; }
.demo-btns { display: flex; gap: 10px; margin-bottom: 16px; flex-wrap: wrap; }
button { padding: 10px 20px; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; transition: all 0.2s; }
.btn-blue { background: #1a73e8; color: #fff; }
.btn-green { background: #34a853; color: #fff; }
.btn-red { background: #ea4335; color: #fff; }
.btn-orange { background: #ff9800; color: #fff; }
.timeline { position: relative; padding-left: 30px; }
.timeline::before { content: ''; position: absolute; left: 10px; top: 0; bottom: 0; width: 3px; background: #e8eaed; }
.timeline-item { position: relative; margin-bottom: 16px; padding: 12px 16px; background: #f8f9fa; border-radius: 8px; transition: all 0.3s; opacity: 0.4; }
.timeline-item.active { opacity: 1; background: #e3f2fd; }
.timeline-item.done { opacity: 0.7; background: #e8f5e9; }
.timeline-item.error { opacity: 1; background: #ffebee; }
.timeline-item::before { content: ''; position: absolute; left: -24px; top: 16px; width: 12px; height: 12px; border-radius: 50%; background: #dadce0; border: 3px solid #fff; }
.timeline-item.active::before { background: #1a73e8; }
.timeline-item.done::before { background: #34a853; }
.timeline-item.error::before { background: #ea4335; }
.step-title { font-weight: 700; font-size: 14px; }
.step-detail { font-size: 12px; color: #666; margin-top: 4px; font-family: 'Consolas', monospace; }
.step-time { font-size: 11px; color: #888; margin-top: 2px; }
.code-block { background: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; font-family: 'Consolas', monospace; font-size: 13px; line-height: 1.7; white-space: pre-wrap; margin-top: 16px; }
</style>
</head>
<body>
<div class="container">
<h1>async/await交互演示</h1>
<div class="card">
<h2>执行模式对比</h2>
<div class="demo-btns">
<button class="btn-blue" onclick="runSerial()">串行执行</button>
<button class="btn-green" onclick="runParallel()">并行执行</button>
<button class="btn-orange" onclick="runError()">错误处理</button>
<button class="btn-red" onclick="resetTimeline()">重置</button>
</div>
<div class="timeline" id="timeline"></div>
</div>
<div class="card">
<h2>代码对比</h2>
<div class="code-block" id="codeBlock">选择执行模式查看代码</div>
</div>
</div>
<script>
const steps = [
{ title: '获取用户信息', detail: 'const user = await fetchUser()', duration: 1000 },
{ title: '获取用户订单', detail: 'const orders = await fetchOrders(user.id)', duration: 800 },
{ title: '获取订单详情', detail: 'const detail = await fetchOrderDetail(orders[0].id)', duration: 600 },
{ title: '获取产品信息', detail: 'const products = await fetchProducts(detail.productId)', duration: 700 },
{ title: '渲染页面', detail: 'renderPage(user, orders, detail, products)', duration: 200 }
];
function renderTimeline() {
document.getElementById('timeline').innerHTML = steps.map((s, i) => `
<div class="timeline-item" id="step-${i}">
<div class="step-title">Step ${i + 1}: ${s.title}</div>
<div class="step-detail">${s.detail}</div>
<div class="step-time">预计耗时: ${s.duration}ms</div>
</div>
`).join('');
}
function updateStep(index, state) {
const el = document.getElementById('step-' + index);
if (el) el.className = `timeline-item ${state}`;
}
function resetTimeline() {
renderTimeline();
document.getElementById('codeBlock').textContent = '选择执行模式查看代码';
}
function simulateStep(index) {
return new Promise(resolve => {
updateStep(index, 'active');
setTimeout(() => {
updateStep(index, 'done');
resolve();
}, steps[index].duration);
});
}
async function runSerial() {
resetTimeline();
const start = Date.now();
for (let i = 0; i < steps.length; i++) {
await simulateStep(i);
}
const elapsed = Date.now() - start;
document.getElementById('codeBlock').textContent = `// 串行执行 - 总耗时约${elapsed}ms
async function serialFetch() {
const user = await fetchUser(); // ${steps[0].duration}ms
const orders = await fetchOrders(user.id); // ${steps[1].duration}ms
const detail = await fetchOrderDetail(orders[0].id); // ${steps[2].duration}ms
const products = await fetchProducts(detail.productId); // ${steps[3].duration}ms
renderPage(user, orders, detail, products); // ${steps[4].duration}ms
}`;
}
async function runParallel() {
resetTimeline();
const start = Date.now();
// 前三个并行,第四个依赖前面的结果
updateStep(0, 'active');
updateStep(1, 'active');
updateStep(2, 'active');
await new Promise(resolve => setTimeout(resolve, Math.max(steps[0].duration, steps[1].duration, steps[2].duration)));
updateStep(0, 'done');
updateStep(1, 'done');
updateStep(2, 'done');
await simulateStep(3);
await simulateStep(4);
const elapsed = Date.now() - start;
document.getElementById('codeBlock').textContent = `// 并行执行 - 总耗时约${elapsed}ms
async function parallelFetch() {
// 无依赖的请求并行执行
const [user, orders, detail] = await Promise.all([
fetchUser(), // ${steps[0].duration}ms
fetchOrders(), // ${steps[1].duration}ms 并行
fetchOrderDetail() // ${steps[2].duration}ms
]);
const products = await fetchProducts(detail.productId);
renderPage(user, orders, detail, products);
}`;
}
async function runError() {
resetTimeline();
await simulateStep(0);
await simulateStep(1);
// Step 3失败
updateStep(2, 'active');
await new Promise(resolve => setTimeout(resolve, steps[2].duration));
updateStep(2, 'error');
document.getElementById('codeBlock').textContent = `// 错误处理
async function fetchWithErrorHandling() {
try {
const user = await fetchUser();
const orders = await fetchOrders(user.id);
const detail = await fetchOrderDetail(orders[0].id); // 这里出错了!
const products = await fetchProducts(detail.productId);
} catch (error) {
// try/catch捕获await抛出的错误
console.error('请求失败:', error.message);
showErrorMessage('加载数据失败,请稍后重试');
} finally {
hideLoadingSpinner();
}
}`;
}
renderTimeline();
</script>
</body>
</html>示例2:循环中的async/await
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>循环中的async/await</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', system-ui, sans-serif; background: #f0f2f5; padding: 20px; color: #333; }
.container { max-width: 900px; margin: 0 auto; }
h1 { text-align: center; color: #1a73e8; margin-bottom: 24px; font-size: 28px; }
.card { background: #fff; border-radius: 12px; padding: 24px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.card h2 { font-size: 18px; color: #1a73e8; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 2px solid #e8eaed; }
.demo-btns { display: flex; gap: 10px; margin-bottom: 16px; flex-wrap: wrap; }
button { padding: 10px 20px; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; }
.btn-blue { background: #1a73e8; color: #fff; }
.btn-green { background: #34a853; color: #fff; }
.btn-orange { background: #ff9800; color: #fff; }
.task-bar { display: flex; gap: 4px; margin-bottom: 16px; align-items: flex-end; height: 120px; }
.bar-item { flex: 1; border-radius: 4px 4px 0 0; transition: height 0.3s, background 0.3s; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; font-size: 11px; color: #fff; font-weight: 600; padding-bottom: 4px; }
.bar-pending { background: #e8eaed; color: #999; }
.bar-running { background: #1a73e8; animation: pulse 0.5s infinite alternate; }
.bar-done { background: #34a853; }
@keyframes pulse { from { opacity: 0.7; } to { opacity: 1; } }
.log-area { background: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; font-family: 'Consolas', monospace; font-size: 13px; line-height: 1.7; max-height: 250px; overflow-y: auto; white-space: pre-wrap; }
.stats { display: flex; gap: 16px; margin-top: 12px; }
.stat { padding: 8px 16px; background: #f8f9fa; border-radius: 6px; font-size: 13px; }
.stat b { color: #1a73e8; }
</style>
</head>
<body>
<div class="container">
<h1>循环中的async/await</h1>
<div class="card">
<h2>执行模式</h2>
<div class="demo-btns">
<button class="btn-blue" onclick="runForOf()">for...of 串行</button>
<button class="btn-green" onclick="runPromiseAll()">Promise.all 并行</button>
<button class="btn-orange" onclick="runLimited()">限制并发(3)</button>
</div>
<div class="task-bar" id="taskBar"></div>
<div class="stats">
<div class="stat">已完成: <b id="doneCount">0</b>/8</div>
<div class="stat">耗时: <b id="elapsed">0</b>ms</div>
</div>
</div>
<div class="card">
<h2>执行日志</h2>
<div class="log-area" id="logArea">等待执行...</div>
</div>
</div>
<script>
const taskCount = 8;
const taskDurations = Array.from({ length: taskCount }, () => 300 + Math.random() * 700);
let taskStates = Array(taskCount).fill('pending');
function renderBar() {
document.getElementById('taskBar').innerHTML = taskDurations.map((d, i) => {
const height = (d / 1000) * 100;
return `<div class="bar-item bar-${taskStates[i]}" style="height:${taskStates[i] === 'pending' ? 20 : height}%">#${i + 1}<br>${Math.round(d)}ms</div>`;
}).join('');
document.getElementById('doneCount').textContent = taskStates.filter(s => s === 'done').length;
}
function simulateTask(index) {
return new Promise(resolve => {
taskStates[index] = 'running';
renderBar();
setTimeout(() => {
taskStates[index] = 'done';
renderBar();
resolve(index);
}, taskDurations[index]);
});
}
function log(msg) {
const area = document.getElementById('logArea');
area.textContent += msg + '\n';
area.scrollTop = area.scrollHeight;
}
async function runForOf() {
taskStates = Array(taskCount).fill('pending');
renderBar();
document.getElementById('logArea').textContent = '';
const start = Date.now();
log('for...of 串行执行开始');
for (let i = 0; i < taskCount; i++) {
log(` 开始任务 #${i + 1}`);
await simulateTask(i);
log(` 完成任务 #${i + 1}`);
}
document.getElementById('elapsed').textContent = Date.now() - start;
log(`串行执行完成,总耗时: ${Date.now() - start}ms`);
}
async function runPromiseAll() {
taskStates = Array(taskCount).fill('pending');
renderBar();
document.getElementById('logArea').textContent = '';
const start = Date.now();
log('Promise.all 并行执行开始');
await Promise.all(taskDurations.map((_, i) => {
log(` 开始任务 #${i + 1}`);
return simulateTask(i).then(() => log(` 完成任务 #${i + 1}`));
}));
document.getElementById('elapsed').textContent = Date.now() - start;
log(`并行执行完成,总耗时: ${Date.now() - start}ms`);
}
async function runLimited() {
taskStates = Array(taskCount).fill('pending');
renderBar();
document.getElementById('logArea').textContent = '';
const start = Date.now();
const limit = 3;
const executing = new Set();
log(`限制并发执行开始 (并发数: ${limit})`);
for (let i = 0; i < taskCount; i++) {
const p = simulateTask(i).then(() => {
executing.delete(p);
log(` 完成任务 #${i + 1}`);
});
log(` 开始任务 #${i + 1}`);
executing.add(p);
if (executing.size >= limit) {
await Promise.race(executing);
}
}
await Promise.all(executing);
document.getElementById('elapsed').textContent = Date.now() - start;
log(`限制并发执行完成,总耗时: ${Date.now() - start}ms`);
}
renderBar();
</script>
</body>
</html>示例3:完整的async/await请求封装
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>完整的async/await请求封装</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', system-ui, sans-serif; background: #f0f2f5; padding: 20px; color: #333; }
.container { max-width: 900px; margin: 0 auto; }
h1 { text-align: center; color: #1a73e8; margin-bottom: 24px; font-size: 28px; }
.card { background: #fff; border-radius: 12px; padding: 24px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.card h2 { font-size: 18px; color: #1a73e8; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 2px solid #e8eaed; }
.code-block { background: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; font-family: 'Consolas', monospace; font-size: 12px; line-height: 1.7; white-space: pre-wrap; margin-bottom: 16px; }
.demo-btns { display: flex; gap: 10px; flex-wrap: wrap; }
button { padding: 10px 20px; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; }
.btn-blue { background: #1a73e8; color: #fff; }
.btn-green { background: #34a853; color: #fff; }
.btn-red { background: #ea4335; color: #fff; }
.result { background: #f8f9fa; padding: 16px; border-radius: 8px; font-family: 'Consolas', monospace; font-size: 13px; min-height: 80px; white-space: pre-wrap; margin-top: 16px; }
</style>
</head>
<body>
<div class="container">
<h1>完整的async/await请求封装</h1>
<div class="card">
<h2>ApiService封装</h2>
<div class="code-block">class ApiService {
constructor(baseURL) {
this.baseURL = baseURL;
this.defaultHeaders = { 'Accept': 'application/json' };
}
// 核心请求方法
async request(url, options = {}) {
const config = {
...options,
headers: { ...this.defaultHeaders, ...options.headers },
signal: options.signal || AbortSignal.timeout(options.timeout || 30000)
};
if (config.body && typeof config.body === 'object') {
config.body = JSON.stringify(config.body);
config.headers['Content-Type'] = 'application/json';
}
try {
const response = await fetch(this.baseURL + url, config);
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new ApiError(response.status, response.statusText, error);
}
if (response.status === 204) return null;
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
throw new ApiError(0, '请求超时或已取消');
}
throw error;
}
}
// 便捷方法
async get(url, params, options) {
const query = params ? '?' + new URLSearchParams(params) : '';
return this.request(url + query, { ...options, method: 'GET' });
}
async post(url, body, options) {
return this.request(url, { ...options, method: 'POST', body });
}
async put(url, body, options) {
return this.request(url, { ...options, method: 'PUT', body });
}
async delete(url, options) {
return this.request(url, { ...options, method: 'DELETE' });
}
// 带重试的请求
async requestWithRetry(url, options, retries = 3) {
for (let i = 0; i <= retries; i++) {
try {
return await this.request(url, options);
} catch (error) {
if (i === retries || error.status < 500) throw error;
const delay = Math.pow(2, i) * 1000;
await new Promise(r => setTimeout(r, delay));
}
}
}
}
class ApiError extends Error {
constructor(status, statusText, data = {}) {
super(`${status} ${statusText}`);
this.status = status;
this.statusText = statusText;
this.data = data;
}
}</div>
</div>
<div class="card">
<h2>使用演示</h2>
<div class="demo-btns">
<button class="btn-blue" onclick="demoBasic()">基本请求</button>
<button class="btn-green" onclick="demoParallel()">并行请求</button>
<button class="btn-red" onclick="demoError()">错误处理</button>
</div>
<div class="result" id="result">点击按钮测试</div>
</div>
</div>
<script>
class ApiError extends Error {
constructor(status, statusText, data = {}) {
super(`${status} ${statusText}`);
this.status = status;
this.statusText = statusText;
this.data = data;
}
}
class ApiService {
constructor(baseURL) {
this.baseURL = baseURL;
this.defaultHeaders = { 'Accept': 'application/json' };
}
async request(url, options = {}) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), options.timeout || 30000);
const config = { ...options, headers: { ...this.defaultHeaders, ...options.headers }, signal: controller.signal };
if (config.body && typeof config.body === 'object') {
config.body = JSON.stringify(config.body);
config.headers['Content-Type'] = 'application/json';
}
try {
const response = await fetch(this.baseURL + url, config);
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new ApiError(response.status, response.statusText, error);
}
if (response.status === 204) return null;
return await response.json();
} catch (error) {
if (error.name === 'AbortError') throw new ApiError(0, '请求超时或已取消');
throw error;
} finally { clearTimeout(timeoutId); }
}
async get(url, params, options) {
const query = params ? '?' + new URLSearchParams(params) : '';
return this.request(url + query, { ...options, method: 'GET' });
}
async post(url, body, options) { return this.request(url, { ...options, method: 'POST', body }); }
}
const api = new ApiService('https://httpbin.org');
async function demoBasic() {
const result = document.getElementById('result');
result.textContent = '请求中...';
try {
const data = await api.get('/get', { name: '张三', role: 'admin' });
result.textContent = `GET请求成功!\n\n${JSON.stringify(data, null, 2)}`;
} catch (error) {
result.textContent = `错误: ${error.message}`;
}
}
async function demoParallel() {
const result = document.getElementById('result');
result.textContent = '并行请求中...';
try {
const [users, posts, comments] = await Promise.all([
api.get('/get', { resource: 'users' }),
api.get('/get', { resource: 'posts' }),
api.get('/get', { resource: 'comments' })
]);
result.textContent = `三个并行请求全部成功!\n\nUsers: ${JSON.stringify(users.args)}\nPosts: ${JSON.stringify(posts.args)}\nComments: ${JSON.stringify(comments.args)}`;
} catch (error) {
result.textContent = `错误: ${error.message}`;
}
}
async function demoError() {
const result = document.getElementById('result');
result.textContent = '测试错误处理...';
try {
await api.get('/status/404');
} catch (error) {
result.textContent = `成功捕获错误!\n\n状态码: ${error.status}\n状态描述: ${error.statusText}\n\n使用try/catch可以优雅地处理async/await中的错误`;
}
}
</script>
</body>
</html>浏览器兼容性
注意事项与最佳实践
1. 优先使用async/await
相比.then/.catch链,async/await更易读、更易调试。
2. 独立请求并行执行
没有依赖关系的请求应使用Promise.all并行执行,避免不必要的串行等待。
3. 始终处理错误
async函数中的未捕获错误会变成unhandled rejection,使用try/catch或.catch()处理。
4. 避免在forEach中使用async
使用for...of或Promise.all + map替代。
5. 注意内存泄漏
长时间运行的async函数中,确保在finally中清理资源。
代码规范示例
标准的async请求模式
代码示例
// 标准模式:加载状态 + 错误处理 + 清理
async function loadUserData(userId) {
let loading = true;
let data = null;
let error = null;
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
data = await response.json();
} catch (err) {
error = err;
console.error('加载用户数据失败:', err);
} finally {
loading = false;
}
return { loading, data, error };
}常见问题与解决方案
问题1:forEach中async不等待
解决方案:使用for...of或Promise.all:
代码示例
// 正确方式1:for...of
for (const url of urls) {
await fetch(url);
}
// 正确方式2:Promise.all
await Promise.all(urls.map(url => fetch(url)));问题2:串行执行太慢
解决方案:分析依赖关系,将独立请求改为并行:
代码示例
// 有依赖:必须串行
const user = await getUser(id);
const orders = await getOrders(user.id);
// 无依赖:应该并行
const [users, products] = await Promise.all([getUsers(), getProducts()]);问题3:错误被吞掉
解决方案:确保每个async函数调用都被await或.catch()处理。
问题4:并发请求过多
解决方案:使用并发控制限制同时执行的请求数量。
总结
async/await是现代JavaScript异步编程的最佳实践:
async函数:声明异步函数,自动返回Promise
await表达式:暂停执行等待Promise解决,让异步代码像同步一样直观
错误处理:使用try/catch捕获await抛出的错误
并行执行:Promise.all实现无依赖请求的并行处理
循环处理:for...of串行、Promise.all并行、并发控制限制
顶层await:ES2022模块中直接使用await
常见陷阱:忘记await、forEach中async、不必要串行
掌握async/await,让异步编程变得简洁优雅,是现代前端开发的必备技能。
常见问题
什么是async/await?
async/await是HTML5开发中的重要技术,本教程详细介绍了其核心概念和实践方法。
如何学习async/await的实际应用?
教程中提供了完整的代码示例和实践指导,建议结合示例代码动手练习。
async/await有哪些注意事项?
常见注意事项包括兼容性、性能优化等,教程的注意事项与最佳实践部分有详细说明。
async/await适合初学者吗?
本教程从基础概念讲起,循序渐进,适合有一定HTML和JavaScript基础的初学者。
async/await的核心要点是什么?
核心要点包括教程简介等内容,建议按教程顺序逐步学习。
本文由小确幸生活整理发布,转载请注明出处
本文涉及AI创作
内容由AI创作,请仔细甄别