前端项目使用await-to-js异步代码优化利器

痛点:async/await 的 try/catch 包装

async/await 写代码很爽,但每个调用要包 try/catch 处理异常,代码很快就会变成下面这样:

async function getList() {
    try {
        const res = await getAListOfClassifiedItems({ p1, p2 })
        if (res.data.code === 200) {
            // 处理成功
        } else {
            console.error('API error:', res.data.code)
        }
    } catch (err) {
        console.error('Network error:', err)
    }
}

三层嵌套已经显得拥挤。一旦多调几次接口,代码深度迅速膨胀,可读性也变差。

await-to-js 这个小工具的思路源自 Go 的错误处理风格 —— 把 (err, data) 作为一个元组返回,调用方判断 err 是否存在。整个 try/catch 不见了。

前端项目使用await-to-js异步代码优化利器

npm 地址:https://www.npmjs.com/package/await-to-js

安装

npm install await-to-js
# 或
pnpm add await-to-js

包体积只有几百字节,完全可以放心装。

基本用法

import to from 'await-to-js'

const list = ref<any[]>([])
async function getList(): Promise<void> {
    const [err, res] = await to(
        getAListOfClassifiedItems_list({
            参数: 参数,
            参数: 参数
        })
    )
    if (err) {
        console.log(err)
        return
    }
    const { data } = res
    if (data.code === 200) {
        console.log(data.code)
    } else {
        console.error('API request failed with code:', data.code)
    }
}

核心模式:

  1. 把 promise 用 to() 包一层
  2. const [err, res] = await to(...) 解构,得到错误和结果
  3. 先判 err,有错就 return / 处理;没错继续往下用 res

跟 try/catch 对比

同一段逻辑两种写法的区别:

// ❌ try/catch 风格 — 容易写成金字塔
async function loadUser(id: number) {
    try {
        const user = await fetchUser(id)
        try {
            const orders = await fetchOrders(user.id)
            try {
                const detail = await fetchUserDetail(user.id)
                return { user, orders, detail }
            } catch (e) {
                console.error('detail error', e)
            }
        } catch (e) {
            console.error('orders error', e)
        }
    } catch (e) {
        console.error('user error', e)
    }
}

// ✓ await-to-js 风格 — 平铺直叙,易读
async function loadUser(id: number) {
    const [e1, user] = await to(fetchUser(id))
    if (e1) return console.error('user error', e1)

    const [e2, orders] = await to(fetchOrders(user.id))
    if (e2) return console.error('orders error', e2)

    const [e3, detail] = await to(fetchUserDetail(user.id))
    if (e3) return console.error('detail error', e3)

    return { user, orders, detail }
}

后者读起来像同步代码,每一步独立判 err,逻辑流清晰。

跟 axios 一起用

import axios from 'axios'
import to from 'await-to-js'

async function saveProfile(data: Profile) {
    const [err, res] = await to(axios.post('/api/profile', data))
    if (err) {
        // axios 错误对象有 response、message 等
        if (err.response) {
            console.error(`服务端错误 ${err.response.status}:`, err.response.data)
        } else {
            console.error('网络错误:', err.message)
        }
        return false
    }
    return res.data.success
}

类型推断

await-to-js 的类型签名:

function to<T, U = Error>(
    promise: Promise<T>,
    errorExt?: object
): Promise<[U, undefined] | [null, T]>

它的返回类型是个判别联合:

  • [Error, undefined] —— 出错时
  • [null, T] —— 成功时

所以 TypeScript 里判完 if (err) return 之后,后面的 res 类型会被自动收窄为 T(非 undefined),不需要手动 ! 断言。

给错误对象塞额外信息

to() 第二个参数可以附加自定义信息到错误对象上:

const [err, res] = await to(
    fetchUser(id),
    { context: 'loadUserProfile', userId: id }
)
if (err) {
    // err 上会有 context 和 userId 属性
    console.error(err.context, err.userId, err.message)
}

调试时挺有用,日志里直接知道是哪个上下文 fail 的。

不要滥用

两个使用边界:

1. 短的单调用,try/catch 反而更直观

就一个 await + 一个错误处理,try/catch 完全没毛病,引入第三方依赖反而没必要。

// 这种场景就用 try/catch
async function ping() {
    try {
        await axios.get('/api/ping')
        return true
    } catch {
        return false
    }
}

2. 上层有统一异常处理时也不需要

如果你的 axios 已经包了拦截器 / 全局错误处理,业务代码里压根不需要每个调用都判 err。直接 await 出来用,出错抛上去给全局处理就行。

类似工具

  • safe-await —— 几乎一样的 API,不同实现
  • neverthrow —— 更"函数式"的 Result 类型,适合复杂错误处理场景,学习成本稍高
  • 自己 3 行写一个 —— 这工具简单到能内联,不想加依赖就自己写:
// utils/to.ts
export function to<T>(p: Promise<T>): Promise<[Error | null, T | undefined]> {
    return p.then(d => [null, d] as [null, T])
            .catch(e => [e, undefined] as [Error, undefined])
}

项目里 import 这个 to 跟 import await-to-js 用法完全一致,自己掌控代码。我个人现在都这么干 —— 一个工具函数不到 5 行,不值得为它加 package.json 一条依赖。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

域名DNS解析中DNS隐/显性 URL记录

2023-9-22 11:45:55

技术教程

解决Vue项目每次更新浏览器缓存问题

2023-10-13 16:52:26

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索