一、Promise 基础
1. 出现背景
JS 传统异步:回调函数,多层嵌套会形成回调地狱,代码嵌套层级深、可读性极差、难以统一错误处理。
Promise 是 ES6 原生异步解决方案,用链式调用扁平化异步代码。
2. Promise 三种状态(不可逆)
pending等待中(初始)fulfilled成功(resolved)rejected失败
状态一旦从 pending 变为成功/失败,就永久凝固,无法再次修改。
3. 基础语法
// 构造函数接收 executor 执行器,同步立即执行constp=newPromise((resolve,reject)=>{// 异步操作:定时器、接口请求、文件读写setTimeout(()=>{constres=Math.random();if(res>0.5){resolve("请求成功数据");// 触发 .then}else{reject("请求失败");// 触发 .catch}},1000);});4. 实例方法
(1).then(onFulfilled, onRejected)
接收成功回调,返回新 Promise,支持链式调用
p.then(data=>{console.log(data);return"下一次then数据";}).then(nextData=>{console.log(nextData);});(2).catch()
捕获失败+ 链式代码内部抛出的异常,统一错误处理
p.then(data=>console.log(data)).catch(err=>console.error("异常:",err));(3).finally()
无论成功失败都会执行,不接收参数,常用于loading关闭、销毁操作
p.then(...).catch(...).finally(()=>{console.log("异步执行完毕");});5. Promise 静态方法(批量异步常用)
| 方法 | 作用 | 结束条件 |
|---|---|---|
Promise.all([p1,p2,p3]) | 所有Promise并行执行 | 全部成功才resolve;任意一个失败立刻reject |
Promise.race([p1,p2]) | 竞速 | 第一个敲定状态(成功/失败)就结束 |
Promise.allSettled() | 等待全部执行完毕 | 不管成功失败,收集所有结果数组 |
Promise.any() | 任一成功即可 | 第一个成功resolve;全部失败才reject |
示例:
constp1=Promise.resolve(1);constp2=Promise.resolve(2);Promise.all([p1,p2]).then(arr=>console.log(arr));// [1,2]二、async / await
ES2017
1. 本质
语法糖,基于 Promise 封装,彻底把异步代码写成同步写法,不再链式.then。
2. 语法规则
async修饰函数:函数永远返回 Promise 对象- return 普通值 → 自动包装为
Promise.resolve(值) - throw 错误 → 自动包装为
Promise.reject(错误)
- return 普通值 → 自动包装为
await只能写在 async 函数内部,不能单独全局使用;
等待 Promise 敲定状态,取出 resolve 的结果,阻塞当前代码往下执行(不阻塞主线程)。
3. 基础示例
// 封装一个Promise请求functionrequestData(){returnnewPromise((resolve)=>{setTimeout(()=>resolve("后端接口数据"),1000);});}// async + awaitasyncfunctiongetData(){console.log("开始请求");constresult=awaitrequestData();// 等待异步结束,拿到结果console.log(result);// 1秒后打印:后端接口数据returnresult;}// async函数调用依旧要用.then或再次awaitgetData().then(res=>console.log(res));4. 错误捕获(关键)
await遇到 reject 会直接抛出异常,必须用try/catch捕获:
asyncfunctionsafeReq(){try{constres=awaitPromise.reject("接口500");console.log(res);}catch(err){console.error("捕获异常:",err);}finally{console.log("执行收尾");}}safeReq();三、链式异步对比(回调地狱 → Promise → async/await)
1. 回调地狱
setTimeout(()=>{console.log(1);setTimeout(()=>{console.log(2);setTimeout(()=>console.log(3),1000);},1000);},1000);2. Promise 链式
newPromise(resolve=>setTimeout(()=>resolve(1),1000)).then(v=>{console.log(v);returnnewPromise(r=>setTimeout(()=>r(2),1000))}).then(v=>{console.log(v);returnnewPromise(r=>setTimeout(()=>r(3),1000))})3. async/await(最优雅)
functiondelay(num){returnnewPromise(resolve=>setTimeout(()=>resolve(num),1000));}asyncfunctionrun(){constn1=awaitdelay(1);console.log(n1);constn2=awaitdelay(2);console.log(n2);constn3=awaitdelay(3);console.log(n3);}run();四、常用实操要点
1. 并行 await(不要串行浪费时间)
多个无依赖接口,不要挨个 await,改用Promise.all并发:
// 串行:总耗时≈2sasyncfunctionserial(){awaitdelay(1000);awaitdelay(1000);}// 并行:总耗时≈1sasyncfunctionparallel(){constp1=delay(1000);constp2=delay(1000);awaitPromise.all([p1,p2]);}2. async 函数返回值细节
asyncfunctionfn(){return100;}console.log(fn());// Promise { 100 }fn().then(v=>console.log(v));// 1003. 顶层 await
ES模块中可用
.mjs模块化文件里,不用包裹 async 函数,直接写:
constdata=awaitfetch("/api/list");五、三者关系总结
- Promise 是底层异步标准,解决回调嵌套;
- async/await 是 Promise 的语法糖,只是写法优化,不能替代Promise;
- 实际开发组合用法:接口函数封装返回 Promise,业务代码用
async await + try/catch编写; - 所有
.then链式场景,都可以等价改写为 await 同步风格代码。
六、经典面试高频题
await后面跟非Promise会发生什么?
自动包装为Promise.resolve(值),立即执行。
await123;// 等价 await Promise.resolve(123)async函数内部await阻塞,会不会卡住浏览器?
不会。只是当前async函数内后续代码暂停执行,JS主线程继续执行其他同步代码,异步入事件队列,不会阻塞页面渲染。try/catch能捕获.then里手动throw的错误吗?
不能,只能捕获当前await抛出的reject异常;.then内部错误只能用自身.catch。