Promise 与 async/await
系列文章目录《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并强调白板复杂度与递归/迭代取舍。