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函数的返回值处理:

返回值 实际返回
普通值 Promise.resolve(值)
Promise 直接返回该Promise
throw错误 Promise.reject(错误)
无返回值 Promise.resolve(undefined)

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>

浏览器兼容性

特性 Chrome Firefox Safari Edge 说明
async/await 55+ 52+ 10.1+ 15+ 广泛支持
async函数 55+ 52+ 10.1+ 15+ 所有形式
顶层await 89+ 89+ 15+ 89+ ES2022
async迭代器 63+ 57+ 12+ 79+ for await...of
async生成器 63+ 55+ 12+ 79+ async function*
AbortSignal.timeout 103+ 100+ 16+ 103+ 较新API

注意事项与最佳实践

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异步编程的最佳实践:

  1. async函数:声明异步函数,自动返回Promise

  2. await表达式:暂停执行等待Promise解决,让异步代码像同步一样直观

  3. 错误处理:使用try/catch捕获await抛出的错误

  4. 并行执行:Promise.all实现无依赖请求的并行处理

  5. 循环处理:for...of串行、Promise.all并行、并发控制限制

  6. 顶层await:ES2022模块中直接使用await

  7. 常见陷阱:忘记await、forEach中async、不必要串行

掌握async/await,让异步编程变得简洁优雅,是现代前端开发的必备技能。

常见问题

什么是async/await?

async/await是HTML5开发中的重要技术,本教程详细介绍了其核心概念和实践方法。

如何学习async/await的实际应用?

教程中提供了完整的代码示例和实践指导,建议结合示例代码动手练习。

async/await有哪些注意事项?

常见注意事项包括兼容性、性能优化等,教程的注意事项与最佳实践部分有详细说明。

async/await适合初学者吗?

本教程从基础概念讲起,循序渐进,适合有一定HTML和JavaScript基础的初学者。

async/await的核心要点是什么?

核心要点包括教程简介等内容,建议按教程顺序逐步学习。

标签: Promise 响应码 async await 场景管理 Fetch API RESTful 游戏状态 请求响应

本文由小确幸生活整理发布,转载请注明出处

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: 后端通信:Promise与异步编程 - 从入门到实践详解 下一篇: 后端通信:RESTful API - 从入门到实践详解

poll相关推荐