【前端实战】从 try-catch 回调到链式调用:一种更优雅的 async/await 错误处理方案

【前端实战】从 try-catch 回调到链式调用:一种更优雅的 async/await 错误处理方案

目录

【前端实战】从 try-catch 回调到链式调用:一种更优雅的 async/await 错误处理方案

一、问题背景:async/await 真的解决了一切麻烦吗?

二、真实业务场景下的痛点

1、错误需要“分阶段处理”

2、try-catch 的引入打破了 async/await 的链式范式

三、借鉴 Go、Rust 语言特性,错误也是一种结果

1、错误优先风格替代 try-catch

2、封装一个 safeAsync 工具函数

四、进阶版 safeAsync 函数设计

五、结语


        作者:watermelo37

        ZEEKLOG优质创作者、华为云云享专家、阿里云专家博主、腾讯云“创作之星”特邀作者、火山KOL、支付宝合作作者,全平台博客昵称watermelo37。

        一个假装是giser的coder,做不只专注于业务逻辑的前端工程师,Java、Docker、Python、LLM均有涉猎。



---------------------------------------------------------------------

温柔地对待温柔的人,包容的三观就是最大的温柔。

---------------------------------------------------------------------

【前端实战】从 try-catch 回调到链式调用:一种更优雅的 async/await 错误处理方案

一、问题背景:async/await 真的解决了一切麻烦吗?

        在 async/await 普及之前,我们的异步代码通常是这样的:

getUserInfo((err, user) => { if (err) { showError() return } getUserDetail(user.id, (err, detail) => { if (err) { showError() return } render(detail) }) }) 

        典型的回调地狱,阅读和维护成本都很高。

        引入 async/await 后,代码变得线性、清晰:

async function loadUser() { try { const user = await getUserInfo() const detail = await getUserDetail(user.id) render(detail) } catch (err) { ElMessage.error('加载失败') } } 

        这已经比回调时代好太多了,但在实际开发中,我遇到了一些问题。

二、真实业务场景下的痛点

1、错误需要“分阶段处理”

        比如一组初始化页面的请求,当然也可以用promise.all()或者promise.allSettled()改写,这里不赘述。

async function initPage() { try { const user = await getUserInfo() const order = await getOrderInfo(user.id) const coupon = await getCoupon(order.id) render({ user, order, coupon }) } catch (err) { ElMessage.error('页面初始化失败') } } 

        但实际需求对不同的错误给出的反馈是不一样的,比如用户信息失败跳登录页,订单失败提示订单异常,优惠券请求失败只给 warning 但不影响主流程等等。

        于是只能写成这样:

async function initPage() { let user try { user = await getUserInfo() } catch (e) { redirectToLogin() return } let order try { order = await getOrderInfo(user.id) } catch (e) { ElMessage.error('订单加载失败') return } let coupon try { coupon = await getCoupon(order.id) } catch (e) { ElMessage.warning('优惠券加载失败') } render({ user, order, coupon }) } 

        伴随着 try-catch 被拆散,控制流被不断打断,就带来了新的问题:本质上形成了新的“结构化回调地狱”。层层叠叠的回调函数非常不优雅

2、try-catch 的引入打破了 async/await 的链式范式

        async/await 本来是一种“像同步一样写异步”(即链式调用)的范式,但大量 try-catch 似乎又把链式调用的范式给拉回了回调层面,不做错误处理又不行,做了错误处理又难看。

        同时,大量 try-catch 又导致逻辑分支碎片化,中间变量暴露在外层作用域,进一步降低了可读性,并提升了变量维护的难度。

        采用控制流的方式处理异步请求的错误情况,就一定会出现这种“悖论”,那怎么办呢?

三、借鉴 Go、Rust 语言特性,错误也是一种结果

1、错误优先风格替代 try-catch

        我意识到,在 Go、Rust 这类语言中,错误并不是通过异常抛出,而是通过返回值体现的,例如:

data, err := getUser() if err != nil { return } 

        这就带来了一种实践思路,如果在 JS 中,把 Promise 的成功和失败都“包装成返回值”,不就可以解决上述问题了吗?

2、封装一个 safeAsync 工具函数

        举个例子:

// utils/safeAsync.js export function safeAsync(promise) { return promise .then(data => [null, data]) // 成功:[null, data] .catch(err => [err, null]) // 失败:[err, null] } 

        这个函数做的事情很简单,永远 resolve,并把错误“降级”为普通返回值,它的本质就是封装了一个函数用来代替 try-catch ,在多请求依赖场景来体现它的价值。

        那么上述的请求场景就可以变成:

async function initPage() { const [userErr, user] = await safeAsync(getUserInfo()) if (userErr) { redirectToLogin() return } const [orderErr, order] = await safeAsync(getOrderInfo(user.id)) if (orderErr) { ElMessage.error('订单加载失败') return } const [couponErr, coupon] = await safeAsync(getCoupon(order.id)) if (couponErr) { ElMessage.warning('优惠券不可用') } render({ user, order, coupon }) } 

        这样就保持了 async/await 的线性结构,错误处理逻辑更加明显更加易读。

四、进阶版 safeAsync 函数设计

        上面已经通过一个基础的 safeAsync 函数解决了回调问题,那 safeAsync 函数能不能有更多设计和可能呢?

        当然可以,我这里给出一种进阶版的设计,各位读者可以根据自己的项目实际情况自由设计和封装自己的 safeAsync 函数:

export async function safeAsync(promise, options = {}) { const { silent = false, // 是否静默失败 toast, // 错误提示文案 onError // 自定义错误回调 } = options try { const data = await promise return [null, data] } catch (err) { if (!silent && toast) { ElMessage.error(toast) } onError?.(err) return [err, null] } } 

        在这个进阶 safeAsync 函数中,除了接受请求返回的 promise 对象外,还收集一个配置项参数 options ,options 中可以传入是否需要弹窗提醒用户,弹窗提示的文案是什么以及自定义错误的回调函数,实现更自由的错误处理。

五、结语

        虽然 async/await 解决了传统回调地狱问题,但 try-catch 可能制造新的结构复杂度,导致代码可读性下降,通过合适的封装和抽象设计,能够大大提升多请求依赖、分阶段错误处理场景下的代码质量。

        只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~

        其他热门文章,请关注:

        极致的灵活度满足工程美学:用Vue Flow绘制一个完美流程图

        你真的会使用Vue3的onMounted钩子函数吗?Vue3中onMounted的用法详解

        Web Worker:让前端飞起来的隐形引擎

        测评:这B班上的值不值?在不同城市过上同等生活水平到底需要多少钱?

        通过array.filter()实现数组的数据筛选、数据清洗和链式调用

        DeepSeek:全栈开发者视角下的AI革命者

        TreeSize:免费的磁盘清理与管理神器,解决C盘爆满的燃眉之急

        通过Array.sort() 实现多字段排序、排序稳定性、随机排序洗牌算法、优化排序性能

        高效工作流:用Mermaid绘制你的专属流程图;如何在Vue3中导入mermaid绘制流程图

        通过MongoDB Atlas 实现语义搜索与 RAG——迈向AI的搜索机制

      【前端实战】如何让用户回到上次阅读的位置?

        前端实战:基于Vue3与免费满血版DeepSeek实现无限滚动+懒加载+瀑布流模块及优化策略

        深入理解 JavaScript 中的 Array.find() 方法:原理、性能优势与实用案例详解

        el-table实现动态数据的实时排序,一篇文章讲清楚elementui的表格排序功能

        JavaScript双问号操作符(??)详解,解决使用 || 时因类型转换带来的问题

        内存泄漏——海量数据背后隐藏的项目生产环境崩溃风险!如何避免内存泄漏

        MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver

        JavaScript中通过array.map()实现数据转换、创建派生数组、异步数据流处理、DOM操作等

Read more

Gemma-3-12B-IT WebUI部署教程:安全加固——反向代理HTTPS、IP白名单、请求频率限制

Gemma-3-12B-IT WebUI部署教程:安全加固——反向代理HTTPS、IP白名单、请求频率限制 1. 前言:为什么你的AI聊天应用需要安全加固? 想象一下这个场景:你刚刚在服务器上部署了Gemma-3-12B-IT的WebUI界面,一个功能强大的AI助手已经准备就绪。它不仅能回答各种问题,还能帮你写代码、做分析、创作内容。你兴奋地把它分享给了几个同事,大家用得都很开心。 但几天后,你发现服务器变得异常缓慢,查看日志时吓了一跳——有大量来自陌生IP地址的请求,有些甚至尝试注入恶意指令。更糟糕的是,由于服务是通过HTTP明文传输的,所有对话内容都可能被中间人窃听。 这不是危言耸听。任何一个暴露在公网上的AI服务,如果没有适当的安全措施,都可能面临这样的风险。今天,我就来分享如何为你的Gemma-3-12B-IT WebUI穿上三层“防护甲”:HTTPS加密传输、IP白名单访问控制、请求频率限制。 这三个措施加在一起,能让你的AI服务既安全又稳定,就像给自家房子装上了防盗门、监控摄像头和访客登记系统一样。 2. 准备工作:了解你的部署环境 在开始安全加固之前,我

WebCoding 开发标准化流程

大家好,今天给大家分享的是WebCoding 开发标准化流程。 1. 需求定义 先把“要做什么”说清楚,再开始写代码。 你要产出这几样东西: * 业务目标:这个网站/系统解决什么问题 * 用户角色:谁在用 * 核心场景:用户完成任务的主路径 * 功能清单:必须有 / 可延期 * 验收标准:什么叫“做完了” 这一步最重要的是把需求写成 用户故事 + 验收条件。 例如: * 用户故事:用户可以注册并登录 * 验收条件:支持邮箱注册、密码重置、登录态保持 7 天、错误提示可读 标准输出: * PRD / 需求文档 * 用户流程图 * 功能优先级列表 * MVP 范围 2. 技术方案设计 需求确认后,不直接开写,而是先定技术方案。 通常要明确: * 前端:

前端数据可视化工具比较:别再为选择工具而烦恼了!

前端数据可视化工具比较:别再为选择工具而烦恼了! 毒舌时刻 数据可视化?听起来就像是前端工程师为了显得自己很专业而特意搞的一套复杂流程。你以为随便用个Chart.js就能做出好看的图表?别做梦了!到时候你会发现,复杂的图表需求根本满足不了。 你以为D3.js是万能的?别天真了!D3.js的学习曲线能让你崩溃,写出来的代码比业务代码还复杂。还有那些所谓的可视化库,看起来高大上,用起来却各种问题。 为什么你需要这个 1. 数据理解:数据可视化可以帮助你更好地理解数据,发现数据中的规律和趋势。 2. 决策支持:可视化的数据可以为决策提供直观的支持,帮助你做出更明智的决策。 3. 用户体验:良好的数据可视化可以提高用户体验,使数据更易于理解和使用。 4. 信息传递:可视化的数据可以更有效地传递信息,减少沟通成本。 5. 品牌形象:专业的数据可视化可以提升品牌的专业形象。 反面教材 // 1. 使用不适合的工具 // 复杂的数据可视化使用Chart.js import Chart from 'chart.js/

爬虫前端调试常见反调试问题及解决方案(超详细实操版)

爬虫前端调试常见反调试问题及解决方案(超详细实操版)

爬虫前端调试常见反调试问题及解决方案(网页实操版) 在爬虫开发过程中,前端调试是获取接口、分析渲染逻辑的关键步骤,但很多网站会设置反调试机制,阻碍我们正常调试。本文整理了7个爬虫前端调试中最常遇到的反调试问题,每个问题都详细说明现象、原因,并给出一步一步的实操解决方案,同时预留截图位置,方便大家插入操作截图,快速上手解决问题。 适用场景:爬虫开发、前端调试、反调试绕过,适合新手入门,也可作为老开发者的调试手册。 问题1:打断点时出现webpack://…相关报错 一、问题现象 在浏览器开发者工具(F12)的Sources面板打断点后,控制台频繁弹出报错,报错信息中包含“webpack://”开头的路径,且断点无法正常触发,调试流程被中断,无法查看代码执行逻辑和参数传递过程。 二、问题原因 这是因为目标网站使用了Webpack打包工具,Webpack在打包时会保留源码的溯源信息,而浏览器开发者工具默认开启了JavaScript溯源功能,会尝试解析Webpack打包后的源码路径,当路径无法匹配或被网站反调试拦截时,就会抛出此类报错,同时干扰断点的正常执行。 三、解决方案(