JavaScript 异步编程

JavaScript 异步编程全面解析

JavaScript的单线程特性使得异步编程成为处理网络请求、文件操作等耗时任务的必然选择。从回调函数到Promise再到async/await,JavaScript异步编程模型持续演进,代码可读性和维护性不断提升。

一、事件循环机制

理解异步编程,必须先了解JavaScript事件循环(Event Loop):

  • JavaScript单线程执行,有一个调用栈(Call Stack)
  • 异步操作(setTimeout、fetch、I/O)完成后,回调函数进入任务队列
  • 事件循环持续检查:调用栈为空时,从任务队列取出回调执行
  • 微任务队列(Promise.then、MutationObserver)优先于宏任务队列(setTimeout、setInterval)

二、回调函数(Callbacks)

// 早期异步模式:回调函数
function getData(url, callback) {
    XMLHttpRequest // ...省略XMLHttpRequest代码
}

// 回调地狱(Callback Hell)- 难以维护
getData('/api/user', function(user) {
    getData('/api/posts/' + user.id, function(posts) {
        getData('/api/comments/' + posts[0].id, function(comments) {
            // 嵌套三层,难以阅读和维护
            console.log(comments);
        }, handleError);
    }, handleError);
}, handleError);

三、Promise:链式调用

// Promise解决回调地狱
function getData(url) {
    return new Promise((resolve, reject) => {
        fetch(url)
            .then(res => res.json())
            .then(resolve)
            .catch(reject);
    });
}

// 链式调用,清晰易读
getData('/api/user')
    .then(user => getData('/api/posts/' + user.id))
    .then(posts => getData('/api/comments/' + posts[0].id))
    .then(comments => console.log(comments))
    .catch(err => console.error('Error:', err));

// Promise.all:并行执行多个Promise
Promise.all([
    fetch('/api/users').then(r => r.json()),
    fetch('/api/products').then(r => r.json()),
    fetch('/api/orders').then(r => r.json()),
]).then(([users, products, orders]) => {
    // 三个请求全部完成后执行
    console.log(users, products, orders);
});

// Promise.allSettled:等待所有Promise完成(不管成功或失败)
Promise.allSettled([p1, p2, p3]).then(results => {
    results.forEach(result => {
        if (result.status === 'fulfilled') console.log(result.value);
        else console.error(result.reason);
    });
});

四、async/await:最佳实践

// async/await让异步代码看起来像同步代码
async function loadUserData(userId) {
    try {
        const user = await fetch('/api/user/' + userId).then(r => r.json());
        const posts = await fetch('/api/posts/' + user.id).then(r => r.json());
        const comments = await fetch('/api/comments/' + posts[0].id).then(r => r.json());
        return { user, posts, comments };
    } catch (err) {
        console.error('Failed to load user data:', err);
        throw err;  // 重新抛出,让调用方处理
    }
}

// 并行请求(不要用串行await!)
async function loadDashboard() {
    // 错误写法:串行等待,总时间 = t1 + t2 + t3
    const users = await getUsers();
    const products = await getProducts();
    const orders = await getOrders();
    
    // 正确写法:并行请求,总时间 = max(t1, t2, t3)
    const [users, products, orders] = await Promise.all([
        getUsers(),
        getProducts(),
        getOrders(),
    ]);
}

五、错误处理最佳实践

// 统一错误处理包装器
async function safeAsync(fn) {
    try {
        const result = await fn();
        return [null, result];
    } catch (err) {
        return [err, null];
    }
}

// 使用方式
const [err, data] = await safeAsync(() => fetch('/api/data'));
if (err) {
    console.error('Request failed:', err);
    return;
}
console.log('Data:', data);

六、生成器与异步迭代

// 异步生成器:处理流式数据
async function* streamData(url) {
    const response = await fetch(url);
    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    
    while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        yield decoder.decode(value);
    }
}

// 使用 for await...of 消费
for await (const chunk of streamData('/api/stream')) {
    processChunk(chunk);
}

七、总结

现代JavaScript异步编程的最佳实践:使用async/await让代码可读;用Promise.all并行执行独立的异步任务;始终处理错误(try/catch或.catch());避免在循环中使用await(改用Promise.all);使用AbortController实现请求取消。掌握这些模式,可以写出高效、可维护的异步代码。