async/await 到底要不要加 try-catch?异步错误处理最佳实践

上周五下午,我正准备下班,产品经理突然跑过来:"用户反馈说提交订单后没反应,是不是又出 bug 了?"

我一查日志,发现接口报了 500 错误,但页面上什么提示都没有。

原来是我写异步请求时忘了加try-catch。用户点完提交就以为成功了,结果订单根本没生成。

那一刻的我才意识到:async/await 错误处理真的不能省。


先来理解 async/await 是什么

简单来说,async/await是处理异步操作的语法糖,让异步代码看起来像同步代码一样直观。

没有 async/await 的时代:

javascript 复制代码
// 回调地狱
fetchData(function(result1) {
  fetchMoreData(result1, function(result2) {
    fetchEvenMoreData(result2, function(result3) {
      // 更多嵌套...
    })
  })
})

有了 async/await 之后:

javascript 复制代码
// 同步般的写法
async function getData() {
  const result1 = await fetchData()
  const result2 = await fetchMoreData(result1)
  const result3 = await fetchEvenMoreData(result2)
  return result3
}

是不是清爽多了?但是,如果 await 后面的 Promise 发生错误(比如网络请求失败),这个错误会直接抛出,如果不捕获,就会导致程序崩溃。


什么时候必须加 try-catch?

1. 需要给用户明确反馈的场景

举个例子:用户点击提交按钮,如果失败了却没有任何提示,用户会以为提交成功,这体验多差啊!

html 复制代码
<template>
  <div>
    <button @click="submitOrder" :disabled="loading">
      {{ loading ? '提交中...' : '提交订单' }}
    </button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      loading: false,
      successMessage: '',
      errorMessage: ''
    }
  },
  methods: {
    async submitOrder() {
      // 开始加载
      this.loading = true
      this.errorMessage = ''
      
      try {
        // 尝试提交订单
        const result = await this.$http.post('/api/orders', this.orderData)
        
        // 提交成功
        this.successMessage = '订单提交成功!'
        this.$router.push('/success') // 跳转到成功页面
        
      } catch (error) {
        // 根据不同错误类型给用户不同的提示
        if (error.response?.status === 401) {
          this.errorMessage = '请先登录后再提交订单'
        } else if (error.response?.status === 400) {
          this.errorMessage = '订单数据有误,请检查后重试'
        } else if (error.response?.status === 500) {
          this.errorMessage = '服务器繁忙,请稍后重试'
        } else {
          this.errorMessage = '网络错误,请检查网络连接'
        }
      } finally {
        // 无论成功失败,都要取消加载状态
        this.loading = false
      }
    }
  }
}
</script>

关键点:

  • 用户操作必须有反馈
  • 不同错误给出不同提示
  • 使用 finally 确保加载状态正确重置

2. 需要继续执行后续逻辑的场景

有时候,即使某个请求失败了,我们仍然希望继续执行其他操作。

javascript 复制代码
async function initializePage() {
  // 获取用户基本信息(重要)
  try {
    this.userInfo = await this.$http.get('/api/user/info')
  } catch (error) {
    console.error('获取用户信息失败,但页面仍可正常使用')
    // 即使失败,也继续执行下面的逻辑
  }
  
  // 获取用户设置(重要)
  try {
    this.userSettings = await this.$http.get('/api/user/settings')
  } catch (error) {
    console.error('获取用户设置失败')
    // 使用默认设置继续
    this.userSettings = this.defaultSettings
  }
  
  // 获取推荐内容(非关键,失败也没关系)
  try {
    this.recommendations = await this.$http.get('/api/recommendations')
  } catch (error) {
    // 静默失败,不影响主要功能
    console.warn('推荐内容加载失败')
  }
}

什么时候可以不加 try-catch?

1. 有全局错误拦截器的情况

如果你的项目配置了全局的 HTTP 拦截器,那么很多错误已经被统一处理了。

javascript 复制代码
// http.js - 全局拦截器
this.$http.interceptors.response.use(
  response => response,
  error => {
    // 全局统一处理错误
    if (error.response?.status === 401) {
      router.push('/login')
    } else if (error.response?.status >= 500) {
      Message.error('服务器错误,请稍后重试')
    }
    return Promise.reject(error)
  }
)

// 组件中 - 不需要重复处理
async fetchData() {
  // 错误已经被全局拦截器处理了
  const data = await this.$http.get('/api/data')
  this.list = data
}

2. 错误需要向上抛出的情况

在编写可复用的函数时,通常不应该在函数内部处理错误,而是让调用方来决定如何处理。

javascript 复制代码
// api/user.js - 用户相关的 API 函数
export const userApi = {
  // 不处理错误,让调用方决定如何处理
  async getUserProfile(userId) {
    const response = await this.$http.get(`/api/users/${userId}`)
    return response.data
  },
  
  async updateUserProfile(userId, profile) {
    const response = await this.$http.put(`/api/users/${userId}`, profile)
    return response.data
  }
}

// 组件中 - 调用方处理错误
export default {
  methods: {
    async loadUserProfile() {
      try {
        this.profile = await userApi.getUserProfile(this.userId)
      } catch (error) {
        this.$message.error('加载用户信息失败')
      }
    }
  }
}

让代码更简洁

每次都写 try-catch 确实有点啰嗦。我们可以封装一个工具函数:

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

使用方式:

js 复制代码
import { safeAsync } from '@/utils/safeAsync'

async function loadUser() {
  const [err, data] = await safeAsync(getUserInfo())
  
  if (err) {
    ElMessage.error('加载失败')
    return
  }
  
  user.value = data
}

这看起来更舒服了吧?这种写法来自 Go 语言的错误优先风格,在 JS 社区也很流行。


多个请求怎么办?先别用 Promise.all

很多朋友喜欢这样写:

js 复制代码
// 危险!一个失败,全部失败
const [user, orders] = await Promise.all([
  getUser(),
  getOrders()
])

但如果 getOrders() 挂了,getUser() 的结果也会丢掉!

正确做法:用 Promise.allSettled

js 复制代码
const results = await Promise.allSettled([getUser(), getOrders()])

const user = results[0].status === 'fulfilled' ? results[0].value : null
const orders = results[1].status === 'fulfilled' ? results[1].value : []

// 即使订单加载失败,用户信息还能显示!

或者用我们上面的 safeAsync

js 复制代码
const [userErr, user] = await safeAsync(getUser())
const [orderErr, orders] = await safeAsync(getOrders())

if (userErr) ElMessage.warning('用户信息加载失败')
if (orderErr) ElMessage.warning('订单加载失败')

全局错误兜底

即使你写了 try-catch,也可能漏掉。所以可以做一些兜底的操作。

比如在全局拦截器处理,或者也可以在 main.js 这样写加个安全网:

js 复制代码
// main.js
const app = createApp(App)

// 全局 Vue 错误处理器
app.config.errorHandler = (err, instance, info) => {
  console.error('Vue 组件错误:', err, info)
  ElNotification.error({
    title: '系统异常',
    message: '页面出现错误,请刷新重试'
  })
}

app.mount('#app')

这样,就算你忘了加 try-catch,也不会让用户看到白屏!


总结

  1. 用户需要反馈时必须加 try-catch
  2. 关键业务流程必须加 try-catch
  3. 有全局处理时可以不加,避免重复
  4. 编写可复用函数时通常不加,让调用方处理
  5. 非关键操作可以不加,或简单处理

错误处理不是一刀切的事情,需要根据具体业务场景来决定。好的错误处理能让你的应用更加健壮,用户体验更好。

感谢观看,希望这篇文章能帮你理清思路!

本文首发于公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

《代码里全是 new 对象,真的很 Low 吗?我认真想了一晚》

《Java 开发必看:什么时候用 for,什么时候用 Stream?》

《这 5 个冷门 HTML 标签,让我直接删了100 行 JS 代码》

《Vue 组件通信的 8 种最佳实践,你知道几种?》

相关推荐
2501_9481953418 分钟前
RN for OpenHarmony英雄联盟助手App实战:符文配置实现
javascript·react native·react.js
小目标一个亿24 分钟前
Windows平台Nginx配置web账号密码验证
linux·前端·nginx
rocky19129 分钟前
网页版时钟
前端·javascript·html
Aotman_38 分钟前
Element-UI Message Box弹窗 使用$confirm方法自定义模版内容,修改默认样式
linux·运维·前端
计算机程序设计小李同学43 分钟前
基于SSM框架的动画制作及分享网站设计
java·前端·后端·学习·ssm
一只小阿乐1 小时前
vue-web端实现图片懒加载的方
前端·javascript·vue.js
牛马1111 小时前
Flutter 多语言
前端·flutter
by————组态2 小时前
集成详细说明
前端·物联网·信息可视化·组态·组态软件
2501_944521002 小时前
rn_for_openharmony商城项目app实战-商品评价实现
javascript·数据库·react native·react.js·ecmascript·harmonyos
我是小疯子662 小时前
jQuery快速入门指南
前端