尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

JS异步地狱避坑:Promise与async/await容易踩的 5 个坑

JS异步地狱避坑:Promise与async/await容易踩的 5 个坑 做前端开发几乎天天和异步打交道。不管是接口请求、定时器、文件处理到处都有异步逻辑。早期全靠回调函数嵌套多了就形成回调地狱代码层层缩进维护起来相当痛苦。Promise 的出现解决了回调嵌套的问题后来 ES2017 推出 async/await基于 Promise 做语法糖写异步代码看起来和同步代码几乎一样上手门槛很低。但语法简单不等于不会踩坑。我见过不少同事写的代码表面上跑起来没问题一旦遇到接口报错、并发量大的时候各种诡异 bug 就冒出来异常悄无声息丢失、接口并发乱序、await 之后代码不按预期执行。很多问题控制台还不会直接报明显错误排查问题要花很久。下面结合实际业务场景盘点 5 个高频踩坑点每个坑都放上错误示例和修复后的代码。坑 1忘记 try‑catch异步异常直接静默丢失这应该是最高频的错误。很多人以为写了 async/await报错就会自动抛出来如果不包 try‑catchPromise 内部 reject 异常会变成未捕获的 Promise 错误部分场景甚至业务逻辑直接中断。错误代码示例// 模拟接口请求随机抛出异常 function fetchUserInfo() { return new Promise((resolve, reject) { setTimeout(() { const errFlag true; if(errFlag) { reject(new Error(获取用户信息接口报错)); }else{ resolve({name:张三}); } }, 800) }) } async function getUser() { // 没有try catchreject之后抛出未捕获异常 const res await fetchUserInfo(); console.log(res); } getUser();上面代码运行控制台会报Uncaught (in promise) Error如果业务没有捕获在部分业务场景比如 Vue、React 项目中会直接导致组件逻辑异常。修复方案使用 try‑catch 捕获异常async function getUser() { try { const res await fetchUserInfo(); console.log(用户数据, res); } catch (err) { console.error(请求出错, err.message); // 做降级处理返回默认数据或者提示用户 } } getUser();补充小提示如果不想每一处都写 try‑catch也可以给 Promise 结尾加上.catch()做兜底。坑 2循环里面写 await误以为实现并发结果串行执行业务中经常会遍历数组发起接口请求很多同学直接在 for 循环里面写 await以为是并发请求实际变成串行请求一个完成才执行下一个数据量大的时候页面加载特别慢。错误写法async function batchRequest(idList) { for(const id of idList) { // 这里会串行执行一个请求结束才下一个 const res await fetchData(id); console.log(res); } } function fetchData(id) { return new Promise(resolve{ setTimeout(()resolve(数据${id}),1000) }) } batchRequest([1,2,3]);上面 3 个请求串行执行总耗时接近 3 秒。想要真正并发先用 map 生成 Promise 任务数组再用Promise.all统一等待。正确并发写法async function batchRequest(idList) { // 先启动所有异步任务 const promiseArr idList.map(id fetchData(id)); // 等待全部完成 const resultList await Promise.all(promiseArr); console.log(resultList); } batchRequest([1,2,3]);⚠️注意Promise.all 有个特性任意一个 Promise reject整体直接失败。如果希望部分失败不影响其他任务可以改用Promise.allSettled。坑 3混淆同步函数和 async 函数忘记返回 Promise很多新手会误以为只要函数里面写了 await函数就是异步。还有一个常见误区async 函数就算内部没有 await返回值也会自动包装成 Promise 对象。async function test() { return 100; } // 错误直接拿返回值拿到的是Promise对象不是100 const num test(); console.log(num); // Promise {fulfilled: 100} // 正确需要await或者.then()获取结果 async function run(){ const num await test(); console.log(num); } run(x3h.ulfvw.cN);经常看到业务代码把 async 函数的返回值直接拿来做判断得到 Promise 对象布尔判断永远为 true造成业务逻辑 bug。坑 4Promise.then 嵌套混用 async/await逻辑混乱有些老项目改造同时使用.then 链式调用又混用 await代码逻辑交织在一起时序变得很难看明白后期维护很折磨人。不推荐写法async function demo() { fetchUserInfo().then(res{ console.log(res) }) await fetchOtherData(); }这里 fetchUserInfo 没有 await不会阻塞会和后面的 await 并发执行很多人误以为会顺序执行。同一个业务逻辑尽量统一风格优先全部使用 async‑await。坑 5Promise.all 一个失败全部崩盘没有做容错上面简单提到 Promise.all这是开发中很容易踩的大坑。多个接口并发只要其中一个接口报错整个 Promise.all 直接 reject其他成功接口的数据全部拿不到。错误示例const p1 Promise.resolve(成功数据1); const p2 Promise.reject(new Error(接口2失败)); const p3 Promise.resolve(成功数据3); // p2报错整体直接失败p1、p3成功的数据拿不到 await Promise.all([p1,p2,p3]);业务场景如果希望部分接口失败不阻塞整体改用Promise.allSettled自己手动过滤成功和失败的结果async function safeBatch() { const p1 Promise.resolve(成功数据1); const p2 Promise.reject(new Error(接口2失败)); const p3 Promise.resolve(成功数据3); const results await Promise.allSettled([p1,p2,p3]); // 筛选成功的结果 const successData results .filter(itemitem.status fulfilled) .map(itemitem.value); console.log(成功的数据, successData); } safeBatch();开发中异步代码的小建议优先统一编码风格要么纯 then 链式要么统一 async/await不要大量混用异步请求一定要处理异常不要放任错误静默消失分清串行和并发循环里 await 默认串行并发使用 Promise.all/allSettled记住 async 函数永远返回 Promise不能直接获取返回值并发接口思考业务容错不是所有场景都适合 Promise.all。写在最后Promise 和 async/await 极大改善了 JS 异步的编码体验告别了回调地狱但语法糖掩盖了底层异步机制。很多坑不是语法本身的问题是我们对异步执行时序理解不到位。写异步代码的时候不要只看代码能不能跑通多思考任务是串行还是并发异常有没有兜底拿到的是原始数据还是 Promise 对象把这几点想清楚大部分异步 bug 都可以提前规避
返回列表