返回博客
技术 2025年2月21日 5 分钟阅读 · 1080 字

JavaScript 异步编程演进:从回调地狱到 async/await 的完整历程

深入理解 JavaScript 异步编程的四个阶段:回调 → Promise → Generator → async/await,以及实际场景中的最佳实践。

#JavaScript #异步编程 #Promise #async/await
本文由 AI 辅助生成,经人工审核发布

回调地狱

JavaScript 最初处理异步的方式是回调函数:

// 回调地狱:层层嵌套
getUser(userId, function(err, user) {
    if (err) return handleError(err);
    
    getOrders(user.id, function(err, orders) {
        if (err) return handleError(err);
        
        getOrderItems(orders[0].id, function(err, items) {
            if (err) return handleError(err);
            
            getProductDetails(items[0].productId, function(err, product) {
                if (err) return handleError(err);
                console.log(product);
            });
        });
    });
});

问题:

  • 代码呈「金字塔」结构,难以阅读
  • 错误处理分散在每个回调中
  • 无法 return/throw——回调的返回值无人接收

Promise

ES6 引入 Promise,将嵌套改为链式调用:

getUser(userId)
    .then(user => getOrders(user.id))
    .then(orders => getOrderItems(orders[0].id))
    .then(items => getProductDetails(items[0].productId))
    .then(product => console.log(product))
    .catch(err => handleError(err));

Promise 的三种状态

pending(等待) → fulfilled(完成)
                ↘ rejected(拒绝)

状态一旦从 pending 变为 fulfilled 或 rejected,就不可逆转。

Promise.all vs Promise.race vs Promise.allSettled

// all:全部成功才成功,一个失败就失败
const [user, settings] = await Promise.all([
    fetch('/api/user').then(r => r.json()),
    fetch('/api/settings').then(r => r.json())
]);

// race:第一个完成的结果(无论成功或失败)
const fastest = await Promise.race([
    fetch('/api/primary'),
    fetch('/api/fallback')
]);

// allSettled:等所有完成,无论成功或失败
const results = await Promise.allSettled([
    fetch('/api/user'),
    fetch('/api/settings'),
    fetch('/api/notifications')
]);
// [{status: 'fulfilled', value: ...}, {status: 'rejected', reason: ...}, ...]
方法用途一个失败
all并行,全部需要整体 reject
race取最快返回该结果
allSettled全部完成不影响其他

async/await

ES2017 引入,让异步代码看起来像同步代码:

async function getProduct(userId) {
    try {
        const user = await getUser(userId);
        const orders = await getOrders(user.id);
        const items = await getOrderItems(orders[0].id);
        const product = await getProductDetails(items[0].productId);
        return product;
    } catch (err) {
        handleError(err);
    }
}

错误处理

// 方式 1:try-catch
try {
    const data = await fetchData();
} catch (err) {
    console.error(err);
}

// 方式 2:.catch()(等价于 try-catch)
const data = await fetchData().catch(err => {
    console.error(err);
    return null;
});

// 方式 3:Go 风格的 helper
async function to(promise) {
    try {
        return [null, await promise];
    } catch (err) {
        return [err, null];
    }
}

const [err, data] = await to(fetchData());
if (err) handleError(err);

并发控制

串行 vs 并行

// ❌ 串行:总时间 = A + B + C
const a = await fetchA();  // 1s
const b = await fetchB();  // 2s
const c = await fetchC();  // 3s
// 总计 6s

// ✅ 并行:总时间 = max(A, B, C)
const [a, b, c] = await Promise.all([
    fetchA(),
    fetchB(),
    fetchC()
]);
// 总计 3s

限制并发数

当需要请求 100 个 API 但不能同时发 100 个请求时:

async function mapWithConcurrency(items, fn, limit) {
    const results = new Array(items.length);
    const executing = new Set();
    
    for (let i = 0; i < items.length; i++) {
        const promise = fn(items[i], i).then(result => {
            results[i] = result;
            executing.delete(promise);
        });
        results[i] = promise;
        executing.add(promise);
        
        if (executing.size >= limit) {
            await Promise.race(executing);
        }
    }
    
    return Promise.all(results);
}

// 最多同时 5 个请求
const results = await mapWithConcurrency(
    urls,
    url => fetch(url).then(r => r.json()),
    5
);

取消异步操作

AbortController 可以取消 fetch 请求:

const controller = new AbortController();

// 5 秒后超时取消
setTimeout(() => controller.abort(), 5000);

try {
    const response = await fetch('/api/slow-endpoint', {
        signal: controller.signal
    });
    const data = await response.json();
} catch (err) {
    if (err.name === 'AbortError') {
        console.log('请求被取消');
    }
}

// 主动取消
controller.abort();

React 中的取消模式

useEffect(() => {
    const controller = new AbortController();
    
    fetch('/api/data', { signal: controller.signal })
        .then(r => r.json())
        .then(setData)
        .catch(err => {
            if (err.name !== 'AbortError') console.error(err);
        });
    
    return () => controller.abort();  // 组件卸载时取消
}, []);

实际场景

重试机制

async function fetchWithRetry(url, retries = 3, delay = 1000) {
    for (let i = 0; i < retries; i++) {
        try {
            const response = await fetch(url);
            if (!response.ok) throw new Error(`HTTP ${response.status}`);
            return await response.json();
        } catch (err) {
            if (i === retries - 1) throw err;
            // 指数退避
            await new Promise(r => setTimeout(r, delay * Math.pow(2, i)));
        }
    }
}

const data = await fetchWithRetry('/api/unstable-endpoint');

缓存 + 过期策略

const cache = new Map();

async function fetchWithCache(key, fetcher, ttl = 60000) {
    const cached = cache.get(key);
    
    if (cached && Date.now() - cached.time < ttl) {
        return cached.value;  // 返回缓存
    }
    
    const value = await fetcher();
    cache.set(key, { value, time: Date.now() });
    return value;
}

// stale-while-revalidate:先返回旧数据,后台更新
async function fetchSWR(key, fetcher, ttl = 60000) {
    const cached = cache.get(key);
    
    if (cached && Date.now() - cached.time < ttl) {
        return cached.value;
    }
    
    if (cached) {
        // 返回旧数据,同时后台更新
        fetcher().then(value => cache.set(key, { value, time: Date.now() }));
        return cached.value;
    }
    
    const value = await fetcher();
    cache.set(key, { value, time: Date.now() });
    return value;
}

顶层 await

ES2022 引入顶层 await,在模块顶层直接使用 await:

// config.js
export const config = await fetch('/api/config').then(r => r.json());

// app.js
import { config } from './config.js';
console.log(config.apiUrl);  // 已就绪

无需包裹在 async 函数中,简化了模块级别的异步初始化。

总结

JavaScript 异步编程的演进方向是让异步代码越来越像同步代码

  1. 回调:功能可用但可读性差
  2. Promise:链式调用,统一错误处理
  3. async/await:语法糖,最佳可读性
  4. AbortController:可取消的异步操作
  5. 顶层 await:模块级异步初始化

实际开发中,async/await + Promise.all 覆盖 95% 的场景。剩余场景用并发控制、重试、缓存等模式解决。