痛点: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 不见了。

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)
}
}
核心模式:
- 把 promise 用
to()包一层 - 用
const [err, res] = await to(...)解构,得到错误和结果 - 先判
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