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