011-JavaScript异步编程详解

JavaScript 异步编程的理解

核心本质

JavaScript 是单线程语言,但浏览器/Node.js 提供了异步非阻塞的运行环境。异步编程的核心目的是解决单线程环境下 I/O 操作等阻塞问题,实现高效并发处理。

关键机制

  1. 事件循环 (Event Loop)

    • 调用栈 (Call Stack):同步代码执行栈
    • 任务队列 (Task Queue):
      • 宏任务队列 (Macrotask):setTimeoutsetInterval、I/O 操作
      • 微任务队列 (Microtask):Promise.then()MutationObserver
    • 执行顺序:同步代码 → 清空微任务队列 → 执行一个宏任务 → 重复
  2. 执行模型

    1
    2
    3
    4
    5
    6
    graph LR
    A[同步代码] --> B{栈空?}
    B -->|是| C[执行微任务]
    C --> D[取宏任务]
    D --> E[执行宏任务]
    E --> C

演进历程

阶段 方案 特点 问题
Callback 回调函数 基础异步支持 回调地狱,错误处理困难
Promise ES6 原生 链式调用,解决回调地狱 仍需 then/catch
Generator function* + yield 暂停/恢复执行 需要执行器(co库)
Async/Await ES2017 同步写法处理异步,终极方案 语法糖(基于 Promise)

代码示例演进

  1. Callback Hell
1
2
3
4
5
6
getData('/api', (err, data1) => {
if (err) handleError(err);
getData(`/api/${data1.id}`, (err, data2) => {
// 更多嵌套...
});
});
  1. Promise 链
1
2
3
4
5
fetch('/api')
.then(response => response.json())
.then(data1 => fetch(`/api/${data1.id}`))
.then(response => response.json())
.catch(err => console.error(err));
  1. Async/Await (推荐)
1
2
3
4
5
6
7
8
9
10
11
async function loadData() {
try {
const res1 = await fetch('/api');
const data1 = await res1.json();

const res2 = await fetch(`/api/${data1.id}`);
return await res2.json();
} catch (err) {
console.error('加载失败', err);
}
}

实际应用场景

  1. 网络请求fetch/axios 数据获取
  2. 定时操作setTimeout/setInterval
  3. 文件操作:Node.js 中 fs.readFile
  4. 用户交互:事件监听 addEventListener
  5. Web Workers:CPU 密集型任务分流

最佳实践

  1. 优先使用 async/await 代替回调
  2. Promise.all() 处理并行异步
1
2
3
4
const [user, posts] = await Promise.all([
fetch('/user'),
fetch('/posts')
]);
  1. 重要操作添加超时控制
1
2
3
4
5
6
7
8
function withTimeout(promise, ms) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('超时')), ms)
)
]);
}

常见误区

  1. 认为 async 函数是同步执行
    (实际只是同步写法,本质仍是异步)

  2. 在循环中错误使用 await

    1
    2
    3
    4
    5
    6
    7
    // 错误:顺序执行
    for (const url of urls) {
    await fetch(url);
    }

    // 正确:并行执行
    await Promise.all(urls.map(url => fetch(url)));
  3. 忽略错误处理
    (未使用 try/catch 或 catch 处理 Promise)

总结

JavaScript 异步编程通过事件循环机制实现单线程下的高并发处理。从回调函数到 Promise 再到 Async/Await 的演进,使异步代码可读性和可维护性大幅提升。理解事件循环、任务队列等底层机制,并合理运用现代异步语法,是编写高效 JavaScript 应用的关键。