同步抛出的错,你的 .catch() 从来没接住过——Promise.try() 进 Baseline 一年,这段代码你还在手写两次
同步抛出错误的函数,你的 .catch() 从来没接住过——它压根没进 Promise 链。Promise.try() 早就进了 Baseline,可这段要写两遍错误处理的代码,大多数项目里还在。
先把坑说清楚。你有没有写过这种代码:调用一个第三方函数,不确定它是同步返回、异步返回,还是直接抛错。于是你老老实实写了 try/catch,又补了一段 .catch()。
try {
const result = thirdParty.doThing(); // 可能同步抛错,也可能返回 Promise
Promise.resolve(result)
.then(processResult)
.catch(handleFailure);
} catch (err) {
handleFailure(err); // 同步抛错根本进不了上面的 .catch()
}
两个错误处理入口,全凭记忆维护。更常见的是这个「看起来更聪明」的写法:
Promise.resolve().then(() => thirdParty.doThing())
它确实能把同步抛错也转成 rejection,但它多引入了一个微任务 tick——函数不再立即执行,而是在下一个微任务里才跑。对于「先同步拿缓存、拿不到再异步请求」这种逻辑,时序就悄悄变了,越查越玄学。
Promise.try() 就是来收这个尾的。
Promise.try(() => thirdParty.doThing())
.then(processResult)
.catch(handleFailure);
同步抛错、返回普通值、返回 Promise,三种情况全部走同一条 .then/.catch。更关键的一点:当回调本身是同步的时候,Promise.try() 是同步执行的,不会像 Promise.resolve().then(fn) 那样被推迟一个微任务。三种起链方式的差别,一张表就够:
Promise.resolve(fn()):立即执行 ✅,同步抛出 ❌Promise.resolve().then(fn):立即执行 ❌,同步抛出 ✅Promise.try(fn):立即执行 ✅,同步抛出 ✅
它是 ES2025 正式特性,MDN 标记 2025 年 1 月起进入 Baseline「新近可用」:Chrome/Edge 128+、Firefox 134+、Safari 18.2+、Node.js 22.7+。也就是说,只要你的运行时不老到离谱,今天就能直接用,一行 polyfill 兜底也才七行:
Promise.try = Promise.try || function (fn, ...args) {
return new Promise((resolve) => resolve(fn(...args)));
};
它生效的原理很简单:Promise 构造函数的执行器本来就是同步跑的,如果 fn() 在里面抛错,构造函数会自动捕获并转成 rejection。所以这个 polyfill 语义上和原生几乎等价,只是少了一点参数透传的细节。
落到前端,最值钱的两个场景。
第一个是「缓存优先、网络兜底」的工具函数:
Promise.try(() => getUserFromCacheOrAPI(userId))
.then(updateUI)
.catch(showErrorBanner)
.finally(() => setLoading(false));
缓存命中时同步返回,未命中时异步请求,错误路径只写一次。
第二个是「校验可能在任何异步开始前就抛错」的提交流程:
Promise.try(() => {
validateInput(formData); // 不合法直接 throw
return submitForm(formData); // 合法则返回 Promise
})
.then(handleSuccess)
.catch(handleError);
校验同步失败、提交异步失败,都落在同一个 catch 里。
需要提醒一句:Promise.try() 是「把不确定的同步/异步调用统一成 Promise」的管道,它不是取消原语。要取消,仍然得配 AbortSignal。
所以下一步很具体:在你的代码库里搜 Promise.resolve().then(,凡是用来「兜住一个不确定会不会同步抛错的函数调用」的,都可以换成 Promise.try();顺带把它旁边那段为同步抛错额外写的 try/catch 删掉。一行代码,少一个错误处理分支。
评论区
登录后可评论。