异步、Promise 与事件循环
异步、Promise 与事件循环 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
这一课用几段「时间可控」的小代码看清 JavaScript 的异步执行顺序,然后把串行、并行、失败处理、超时这四个武器配齐。读完你应该能预测一段含 setTimeout 和 Promise 的代码的输出顺序,并写出可靠的批量任务代码。
问题场景
你要同时抓取三个课程接口。第一版用 for 循环逐个 await,三个请求一个接一个,总耗时是三者之和;第二版听说要「并行」,改成 Promise.all,结果其中一个接口 404,三个结果全部作废。串行太慢,并行又怕一颗老鼠屎坏一锅粥——先搞懂事件循环,这些选择就不难了。
核心解释
JavaScript 是单线程的:同一时刻只执行一段代码。异步任务不是「同时做」,而是「排进队列稍后再做」,调度者叫事件循环(event loop,不断从队列里取任务执行的机制)。规则两条:
- 同步代码一口气跑完;
- 然后先清空微任务(microtask,Promise 回调所在的队列),再取一个宏任务(macrotask,比如 setTimeout 的回调);每取一个宏任务前后,都会先清空微任务。
console.log('1 同步');
setTimeout(() => console.log('4 宏任务'), 0);
Promise.resolve().then(() => console.log('3 微任务'));
console.log('2 同步');
// 输出顺序:1 2 3 4 —— 微任务插队,setTimeout(0) 也不是「立刻」
async/await 是 Promise 的语法糖:await 之后的代码相当于放进 then 的回调,也就是微任务。所以「在循环里 await 一串任务」是串行(一个完成才下一个);想要并行,先把 Promise 创建出来再一起 await:
// 串行:总耗时 ≈ a + b
const a = await fetchA();
const b = await fetchB();
// 并行:总耗时 ≈ max(a, b) —— 两个请求同时出发,一起等
const [ra, rb] = await Promise.all([fetchA(), fetchB()]);
示例拆解
失败处理按「能不能接受部分失败」选工具:
const results = await Promise.allSettled([fetchA(), fetchB(), fetchC()]);
const ok = results.filter((r) => r.status === 'fulfilled').map((r) => r.value);
const failed = results.filter((r) => r.status === 'rejected');
console.log(ok, failed); // 成功的照常用,失败的单独提示
Promise.all:一个失败立即整体拒绝,适合「缺一个就没意义」的场景,比如一次页面渲染的必需数据。Promise.allSettled:每个任务都给结果,适合互相独立的任务,比如抓多个可互相替代的数据源。- 超时用
Promise.race(谁先结束就用谁的结果):让「任务」和「定时 reject 的闹钟」赛跑,闹钟先响就按超时处理,避免永远等下去。
动手任务
打开本课实验区(输出都带毫秒时间戳):
- 先在注释里写下实验区第一段代码的完整输出顺序,再运行对照——顺序错一个都算没过关。
- 把实验区里「串行抓两门课」改成并行,记录两种写法在控制台显示的总耗时差。
- 用
Promise.race给并行抓取加 800ms 超时:超时时输出「请求超时」,而不是永远等下去。
常见错误
- 在 for 循环里 await 还以为是并行:循环里 await 是串行;要并行先收集 Promise,再交给
Promise.all。 - 以为 setTimeout(fn, 0) 立刻执行:它至少要等同步代码和全部微任务结束。
- async 函数里 try/catch 漏掉 await:
fetch(...)不加 await 就不会把错误抛进 catch,异常处理形同虚设。 - 创建了 Promise 却没人接:调用 async 函数返回的 Promise 没有 await 或 catch,失败会变成「未处理的 promise 拒绝」,控制台报错但代码继续跑。
检查与复盘
完成下方检查题确认理解。自查清单:
- 能说出「同步 → 微任务 → 宏任务」的执行顺序并正确预测输出
- 能为「必需数据」和「可缺数据」分别选对 all 和 allSettled
- 知道串行与并行的耗时差别,并且亲手测过一次
参考资料
- MDN:使用 Promise —— Promise 的核心用法
- MDN:并发模型与事件循环 —— 执行顺序的正式解释
- MDN:async 函数 —— await 的语义
- MDN:Promise.all —— 并行与「一损俱损」的行为
读完这一课了?
标记完成后,工作台会安排接下来的学习。