JavaScript 异步编程
1. 为什么 JavaScript 是单线程的?
JavaScript 最初是为了网页交互而设计的。设想如果它是多线程的,一个线程在 DOM 节点上添加内容,另一个线程删除了这个节点,浏览器应该听谁的?这将带来极高的同步复杂性。
因此,JavaScript 选择单线程,意味着它在同一时间只能做一件事。
为了不让耗时任务(如请求接口、读取文件)卡死主线程(导致页面无响应),JavaScript 引入了异步非阻塞机制。
2. 异步编程的进化史
我们处理异步的方式经历了几个阶段:
(1) 回调函数 (Callback)
最原始的方案。通过将函数作为参数传递,在任务完成后调用。
缺点:容易陷入“回调地狱”(Callback Hell),层层嵌套导致代码难以维护和调试。
(2) Promise (ES6)
为了解决回调地狱,Promise 出现了。它代表了一个异步操作的最终结果。
特点:
- 状态管理:
pending->fulfilled(成功) 或rejected(失败)。状态一旦改变不可逆。 - 链式调用:通过
.then()将异步步骤串联起来,使代码变扁平。
(3) Generator (生成器)
Generator 函数是可以“暂停”和“恢复”执行的函数。它虽然不是专门为异步设计的,但配合执行器(Executor)可以巧妙地管理异步流程。
function*声明生成器。yield关键字用来暂停并在暂停处输出一个值。.next()方法用来恢复执行。
基本用法示例:
在 async/await 出现之前,著名的 co 库就是利用 Generator 实现了类似于同步写法的异步控制。
(4) Async/Await (ES8)
这是目前的终极解决方案。简而言之,Async/Await 就是 Generator + Promise 的语法糖。
它让异步代码看起来完全像同步代码,极大地提高了可读性。
async函数隐式返回一个 Promise。await会暂停代码执行,直到等待的 Promise 解决(resolved)。- 可以使用标准的
try...catch进行错误捕获。
3. Promise 核心 API
除了基础的 then/catch,掌握这些静态方法很重要:
Promise.all([p1, p2]): “并”。所有都成功才算成功,有一个失败就立即失败。Promise.race([p1, p2]): “赛跑”。哪个结果跑得快(无论成功失败)就用哪个。Promise.allSettled([p1, p2])(ES2020): “汇报总结”。等所有都结束,返回每个任务的状态和结果(不会因为一个失败就中断)。Promise.any([p1, p2])(ES2021): “求胜”。只要有一个成功这就成了;只有全部失败才返回失败。
4. 核心机制:事件循环 (Event Loop)
事件循环由浏览器宿主环境负责调度,见浏览器事件循环。
5. 高频面试题
Q: 宏任务和微任务的区别?执行顺序?
答:任务和微任务处于不同的调度阶段。当前任务执行完、调用栈清空后,浏览器会执行微任务检查点并清空微任务队列,然后才有机会渲染和执行下一个任务。
Q: async/await 原理是什么?
答:它是 Generator 的语法糖。Generator 可以暂停/恢复,配合一个自动执行器(auto runner),在 yield 一个 Promise 时暂停,等 Promise resolve 后自动调用 next() 并把结果传回,从而通过同步的写法完成异步流程。
Q: 手写 Promise 的思路?
(核心是状态管理 + 发布订阅模式。具体见 q-promise-implementation.md)

