系列文章目录《JavaScript 基础与进阶笔记》前期偏基础巩固与常见面试点后续进入闭包、异步、工程化等进阶主题第 01 篇数据类型与类型判断第 02 篇变量声明与作用域第 03 篇闭包与高阶函数第 04 篇函数工厂第 05 篇this 指向与绑定第 06 篇原型与原型链第 07 篇类与继承第 08 篇JS 执行机制与异步队列第 09 篇数组常用方法第 10 篇字符串算法第 11 篇常见手写题合集上第 12 篇常见手写题合集下第 13 篇Promise 与 async/await本文文章目录系列文章目录前言一、Promise 核心机制复习与深化1.1 三态与不可逆1.2 then 必返回新 Promise1.3 值穿透1.4 「Promise 本身是同步的」——面试怎么答二、静态方法all / race / allSettled / any2.1 对照总表2.2 Promise.all2.3 Promise.race2.4 Promise.allSettled2.5 Promise.any2.6 选型口诀三、超时控制race 模式四、重试async/await 实现五、并发池限制同时进行的任务数六、async/await 工程实践6.1 定义与 Promise 的对应6.2 串行 vs 并行6.3 错误处理6.4 finally七、静态方法速查八、易混淆点归纳九、思考与练习总结前言第 12 篇已从「手写 Promise」切入 async/await 与 LRU本篇作为Promise 专题系统梳理三态与链式规则、静态方法全家桶all/race/allSettled/any以及工程里常见的超时、重试、并发池写法。面试高频表述题——「Promise 本身是同步的」——也会单独说明并与第 08 篇宏任务/微任务对照。读完后应能根据场景选对 API并用 async/await 写出可维护的异步流程。一、Promise 核心机制复习与深化1.1 三态与不可逆状态含义可转移pending待定初始态→ fulfilled / rejectedfulfilled已成功不可再变rejected已失败不可再变一旦settledfulfilled 或 rejected状态不可回退多次resolve/reject只有第一次生效。1.2then必返回新 Promise.then(onFulfilled, onRejected)每次调用都返回全新的 Promise这是链式调用的基础constp2Promise.resolve(1).then((x)x1);constp3p2.then((x)x*2);p2.then(console.log);// 2p3.then(console.log);// 4规则摘要回调返回普通值→ 下一个then收到该值被Promise.resolve包装。回调返回 Promise / thenable→ 下一个then等待其状态再决定 fulfilled/rejected。回调throw或返回 rejected Promise → 后续 fulfilled 跳过进入rejected链直到被catch处理。1.3 值穿透当onFulfilled或onRejected不是函数时值会原样传递到下一个thenPromise.resolve(42).then(null,()ignored).then((v)console.log(v));// 42Promise.reject(err).then((v)v).then(null,undefined)// 非函数rejection 继续穿透.catch((e)console.log(e));// errcatch(fn)等价then(null, fn)若catch不抛错且返回普通值后续then会回到 fulfilled 链。1.4 「Promise 本身是同步的」——面试怎么答这句话不是说 Promise 没有异步而是指new Promise(executor)里的executor函数同步执行resolve/reject调用前的代码都在当前调用栈跑完。.then/.catch/.finally注册的回调才是微任务在本轮同步代码结束后、下一个宏任务前执行见第 08 篇。async函数被调用时函数体执行到第一个await之前是同步的await之后的代码相当于then回调属于微任务。console.log(1);newPromise((resolve){console.log(2);// executor 同步resolve();}).then(()console.log(3));// 微任务asyncfunctionf(){console.log(4);// 同步awaitPromise.resolve();console.log(5);// 微任务}f();console.log(6);// 1 → 2 → 4 → 6 → 3 → 5标准答法Promise 的创建与 executor 是同步的异步感来自 then 回调与 await 续体进入微任务队列。二、静态方法all/race/allSettled/any四个方法都接收Iterable常用数组返回新的 Promise差异在聚合语义。2.1 对照总表方法成功条件失败条件典型用途Promise.all全部fulfilled任一rejected多接口并行缺一不可Promise.race最先 settle的那个最先 reject 则失败超时、竞速Promise.allSettled全部settle无论成败不会 reject批量任务要完整结果Promise.any任一fulfilled全部rejected多源备份有一个成功即可2.2Promise.allconstdelay(ms,val)newPromise((r)setTimeout(()r(val),ms));Promise.all([delay(100,a),delay(50,b)]).then(console.log);// [a, b] — 顺序与传入数组一致与完成先后无关Promise.all([Promise.resolve(1),Promise.reject(newError(fail)),]).then(console.log).catch((e)console.log(e.message));// fail — 快速失败要点元素会先经Promise.resolve包装。空数组→ 立即resolve([])。一个失败整体失败其他 Promise仍会各自执行完但all的结果已是 rejected。2.3Promise.racePromise.race([delay(200,slow),delay(50,fast),]).then(console.log);// fastPromise.race([Promise.reject(newError(err)),delay(1000,ok),]).catch((e)console.log(e.message));// err — 先 reject 的先定结果要点第一个 settledfulfilled 或 rejected决定返回值其余任务不会被取消fetch 等仍会继续需AbortController才能真正中止。2.4Promise.allSettledPromise.allSettled([Promise.resolve(1),Promise.reject(newError(x)),delay(10,3),]).then(console.log);// [// { status: fulfilled, value: 1 },// { status: rejected, reason: Error: x },// { status: fulfilled, value: 3 }// ]要点永不 reject除非 iterable 本身抛同步错适合「批量上报、部分失败仍要汇总」的场景。2.5Promise.anyPromise.any([Promise.reject(newError(a)),delay(30,backup),delay(100,slow),]).then(console.log);// backupPromise.any([Promise.reject(newError(a)),Promise.reject(newError(b)),]).catch((e)console.log(e.errors.length));// 2 — AggregateError要点与all相反——任一成功即成功全部失败才 reject错误类型为AggregateError.errors为各 rejection 数组。2.6 选型口诀都要成功→all要比谁快→race都要结果含失败→allSettled有一个就行→any三、超时控制race模式请求无内置超时时常用Promise.race([task, timeoutPromise])consttimeout(ms,msgtimeout)newPromise((_,reject)setTimeout(()reject(newError(msg)),ms));constfetchWithTimeout(url,ms5000)Promise.race([fetch(url),timeout(ms)]);fetchWithTimeout(/api/data,3000).catch((e)console.log(e.message));注意先 reject 的胜出——若请求在超时后完成fetch仍在进行可能造成浪费生产环境配合AbortController取消请求。超时 Promise不会自动取消另一路race只决定谁先改变聚合 Promise 的状态。constfetchWithTimeout(url,ms5000){constcontrollernewAbortController();consttimersetTimeout(()controller.abort(),ms);returnfetch(url,{signal:controller.signal}).finally(()clearTimeout(timer));};四、重试async/await 实现接口偶发失败时可在async函数里用try/catch 循环重试constretryasync(fn,times3,delayMs0){for(leti0;itimes;i){try{returnawaitfn();}catch(e){if(itimes-1)throwe;if(delayMs)awaitnewPromise((r)setTimeout(r,delayMs));console.warn(retry${i1}/${times});}}};letattempt0;constunstableasync(){if(attempt3)thrownewError(fail);returnok;};retry(unstable,3).then(console.log);// ok — 第 3 次成功变体指数退避delayMs * 2 ** i减轻服务端压力。只重试特定错误catch里判断e.status 503再 continue。与race组合单次请求限时 整体重试次数上限。五、并发池限制同时进行的任务数Promise.all(tasks.map(fn))会同时发起全部请求任务很多时需要并发上限如同时最多 3 个constlimitPoolasync(tasks,limit2){constresults[];letindex0;constworkerasync(){while(indextasks.length){constiindex;results[i]awaittasks[i]();}};awaitPromise.all(Array.from({length:Math.min(limit,tasks.length)},worker));returnresults;};constdelay(ms,v)newPromise((r)setTimeout(()r(v),ms));consttasks[1,2,3,4,5].map((n)()delay(100,n).then((v){console.log(done,v,Date.now());returnv;}));limitPool(tasks,2).then(console.log);// 同一时刻最多 2 个 done最终 [1,2,3,4,5]思路开limit个 worker共享递增下标index每个 worker 循环取任务直到取完用Promise.all等待所有 worker 结束。与all区别all管「是否全部完成」并发池管「同时进行几个」。六、async/await 工程实践6.1 定义与 Promise 的对应async函数返回值一定是Promisereturn v→Promise.resolve(v)throw e→Promise.reject(e)。await等待 Promise 定型resolved 得值rejected 等价throw。async/await是 Promise 的语法糖await之后的代码等价于.then里的续体。6.2 串行 vs 并行constdelay(ms,v)newPromise((r)setTimeout(()r(v),ms));// 串行 — 总耗时约 300msfor(constidof[1,2,3]){awaitdelay(100,id);}// 并行 — 总耗时约 100msawaitPromise.all([1,2,3].map((id)delay(100,id)));易错循环里写async回调但不await会导致并发失控且错误难捕获// ❌ forEach 不会等待 async 回调urls.forEach(async(url){awaitfetch(url);});// ✅ for...of await或 Promise.all mapfor(consturlofurls)awaitfetch(url);awaitPromise.all(urls.map((url)fetch(url)));6.3 错误处理constloadasync(){try{constaawaitfetch(/a);constbawaitfetch(/b);return[a,b];}catch(e){console.error(请求失败,e);throwe;}};try/catch可捕获链中await的 rejection等价于.then().catch()。顶层 async 函数若未 catch需.catch()或 void 调用处处理避免Unhandled rejection。6.4finallyconstloadWithLoadingasync(){showLoading();try{returnawaitfetchData();}finally{hideLoading();// 无论成败都会执行}};finally不接收前序结果也不改变 fulfilled/rejected除非finally内throw或return Promise。七、静态方法速查APIresolve 时机reject 时机Promise.resolve(x)立即 fulfilled—Promise.reject(e)—立即 rejectedPromise.all(arr)全部 fulfilled任一 rejectedPromise.race(arr)最先 fulfilled最先 rejectedPromise.allSettled(arr)全部 settle几乎不 rejectPromise.any(arr)任一 fulfilled全部 rejected八、易混淆点归纳「Promise 同步」executor 同步then/await 续体是微任务勿理解成「没有异步」。allvsraceall 要全部成功race 要最先定型的那一个。allvsanyall一败俱败any一成则成。allSettledvsall前者要完整报告后者快速失败。race超时不会自动取消慢任务需要AbortController等机制。await在 for 里默认串行并行必须Promise.all。catch之后若返回普通值后续then走fulfilled错误链已「修复」。九、思考与练习1.Promise.all([p1, p2])与Promise.race([p1, p2])在 p1、p2 都 fulfilled 时结果分别是什么解析all→[v1, v2]数组顺序同传入race→先 fulfilled 的那个值单个值不是数组。2.下面输出顺序console.log(1);Promise.resolve().then(()console.log(2));asyncfunctionf(){console.log(3);awaitnull;console.log(4);}f();console.log(5);解析1 → 3 → 5 → 2 → 4。await null等价await Promise.resolve(null)续体进微任务。3.三个 CDN 镜像任一成功即可加载脚本用哪个 API解析Promise.any。4.批量删除 100 条记录需知道每条成功或失败原因用哪个 API解析Promise.allSettled再遍历status/value/reason。5.并发池limit 2、任务 5 个同一时刻最多几个在执行解析2 个一个 worker 完成后才会从共享队列取下一个下标。总结机制三态不可逆then返回新 Promise值穿透与rejected 穿透executor 同步、then/await 微任务。静态方法all全成则成race先到先得allSettled全量报告any任一成功。工程模式race 超时配合 abort、retry 循环、limitPool 并发池。async/await语法糖并行用Promise.alltry/catch/finally组织错误与清理。下一篇进入数据结构基础栈与队列、链表、二叉树、哈希与Map/Set并强调白板复杂度与递归/迭代取舍。