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

目录

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

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

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

1、错误需要"分阶段处理"

[2、try-catch 的引入打破了 async/await 的链式范式](#2、try-catch 的引入打破了 async/await 的链式范式)

[三、借鉴 Go、Rust 语言特性,错误也是一种结果](#三、借鉴 Go、Rust 语言特性,错误也是一种结果)

[1、错误优先风格替代 try-catch](#1、错误优先风格替代 try-catch)

[2、封装一个 safeAsync 工具函数](#2、封装一个 safeAsync 工具函数)

[四、进阶版 safeAsync 函数设计](#四、进阶版 safeAsync 函数设计)

五、结语


作者:watermelo37

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

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


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


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

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

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

javascript 复制代码
getUserInfo((err, user) => {
  if (err) {
    showError()
    return
  }
  getUserDetail(user.id, (err, detail) => {
    if (err) {
      showError()
      return
    }
    render(detail)
  })
})

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

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

javascript 复制代码
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()改写,这里不赘述。

javascript 复制代码
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 但不影响主流程等等。

于是只能写成这样:

javascript 复制代码
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 这类语言中,错误并不是通过异常抛出,而是通过返回值体现的,例如:

Go 复制代码
data, err := getUser()
if err != nil {
  return
}

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

2、封装一个 safeAsync 工具函数

举个例子:

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

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

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

javascript 复制代码
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 函数:

javascript 复制代码
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操作等

相关推荐
MSTcheng.6 小时前
【C++】如何快速实现一棵支持key或key-value的二叉搜索树?关键技巧一文掌握!
开发语言·c++·算法·二叉搜索树
木易 士心6 小时前
NestJS 核心揭秘:InstanceWrapper 的艺术与前端缓存新思路
前端·缓存
Andre_BWM99926 小时前
跨境电商防关联技术实践:美客多自养号环境搭建(指纹浏览器/IP隔离/支付合规)
网络·网络协议·tcp/ip
古韵6 小时前
当 API 文档走进编辑器会怎样?
vue.js·react.js·node.js
野生风长6 小时前
从零开始的c语言:指针高级应用(下)(回调函数,qsort函数模拟实现, strlen和sizeof)
java·c语言·开发语言·c++·算法
IT_陈寒6 小时前
SpringBoot 3.x性能优化实战:这5个配置让你的应用启动速度提升50%
前端·人工智能·后端
奶昔不会射手6 小时前
css之如何获取祖先元素的宽高
前端·css
serve the people6 小时前
滑块验证完整实现教程(前端 + 后端 + Nginx 集成)
运维·前端·nginx
yivifu6 小时前
Excel中Lookup函数实现临界点归入下一个等级的方法
java·前端·excel