Promise 再次进化,ES2025 新增 Promise.try() 静态方法

全文速览

欢迎关注 前端情报社。大家好,我是社长林语冰。

Promise 从 ES2015 成为 JavaScript 的一部分。10 年后,ES2025 是第 16 版 JavaScript 语言规范,它新增了 9 种颠覆性功能,Promise.try() 就是其中之一。

顾名思义,Promise.try()Promise 类新增了一个静态方法,它接收一个 行为不可知的阻塞型回调

  • 它可能是异步函数;
  • 它可能返回 promise;
  • 它可能引发异常
  • .....

然后 立即调用 该回调,最终返回一个 promise。

本文我们会探讨 ES2025 最新 Promise.try() 静态方法的基本用法,高级用例,底层原理和编程技巧。

ES2025 Promise.try()

Promise.try() 提案并非原创,ES2025 之前,bluebird 和 p-try 等流行库就提供了等价的功能。

bluebird 官方文档提供了基本示例:

js 复制代码
function getUserById(id) {
  return Promise.try(function () {
    if (typeof id !== 'number') {
      throw new Error('id 要求为数字!')
    }
    return db.getUserById(id)
  })
}

getUserById().catch(console.log)
// Error: id 要求为数字!

现实开发中的代码往往错综复杂,有的业务逻辑可能混用同步/异步操作。上述代码中,输入验证是同步错误,数据库操作可能是异步操作。Promise.try() 可以用于封装这些复杂业务,确保无论回调是否异步执行或报错,都能返回一个 promise,继续链式调用。

这就是 ES2025 Promise.try() 的用途,但我们不需要再安装 bluebird 等第三方库。

具体而言,Promise.try() 接受一个行为不可知的 阻塞型回调 并立即调用它,最终返回 promise:

javascript 复制代码
// ES2025 之后的写法:
// ✅️ 1. 回调返回非 promise
Promise.try(() => '同步结果').then(console.log)

// ✅️ 2. 回调同步报错
Promise.try(() => {
  throw new Error('同步异常')
}).catch(console.log)

// ✅️ 3. 回调返回成功的 promise
Promise.try(() => Promise.resolve('fulfillment')).then(console.log)

// ✅️ 4. 回调返回失败的 promise
Promise.try(() => Promise.reject('rejection')).catch(console.log)

// ✅️ 5. 回调是正常执行的异步函数
Promise.try(async () => {
  let data = await Promise.resolve('异步结果')
  // 其他业务......
  return data
}).then(console.log)

// ✅️ 6. 回调是异步报错的异步函数
Promise.try(async () => {
  try {
    let result = await Promise.reject('异步异常')
  } catch (e) {
    throw e
  }
}).catch(console.log)

可以看到,Promise.try() 是一个更加强大和通用的现代原生 API,适用于各种复杂的回调场景。

另请参考,其 TypeScript 源码的函数签名如下:

ts 复制代码
interface PromiseConstructor {
  try<T, U extends unknown[]>(
    callbackFn: (...args: U) => T | PromiseLike<T>,
    ...args: U
  ): Promise<Awaited<T>>
}

底层原理

ES2025 之前,想要实现 Promise.try() 的等价功能,除了引入第三方模块,还可以使用 new Promise() 手动封装,只要你懂得基本的底层原理。

具体而言,new Promise() 模拟 Promise.try() 的底层原理如下:

js 复制代码
Promise.try = function (f, ...args) {
  return new Promise((resolve) => {
    resolve(f(...args))
  })
}

这里,new Promise() 内部调用回调,同时将返回值封装为一个 promise 实例。

比起安装第三方模块或手动封装,ES2025 原生的 Promise.try() 显然更符合人体工程学。

不同于 new Promise(resolve => resolve(f())) 这种遗臭万年的代码屎山,Promise.try(f) 是一种更精简的"代码高尔夫":你能用 更少的字符 重构等价的功能。

薛定谔的异步

现实开发中,某些 API 的回调可能同步/异步执行:

js 复制代码
let map = new Map([[1, 'cache']])

function log(data) {
  console.log(`callback: ${data}`)
}

function zalgoAPI(id, cb) {
  if (map.has(id)) {
    // 若缓存命中,则回调同步执行
    cb(map.get(id))
  } else {
    // 若缓存未命中,则回调异步执行
    setTimeout(() => {
      map.set(id, 'update data')
      cb(map.get(id))
    }, 1_000)
  }
}

console.log('sync:', 1)
zalgoAPI(1, log)
zalgoAPI(2, log)
console.log('sync:', 2)
/**
 * sync: 1
 * callback: cache
 * sync: 2
 * callback: update data
 */

"npm 之父"将这种难以预测的设计屎山称为 Zalgo 问题(混沌问题)。

为了解决 Zalgo 问题,我们可以使用 Promise.try() 简单重构,确保回调始终异步执行:

js 复制代码
import { setTimeout as setTimeoutPromise } from 'node:timers/promises'

let map = new Map([[1, 'async cache']])

function asyncAPI(id) {
  return Promise.try(() => {
    if (map.has(id)) {
      return map.get(id)
    } else {
      return setTimeoutPromise(1_000).then(() => {
        map.set(id, 'async data')
        return map.get(id)
      })
    }
  })
}

console.log('sync:', 1)
asyncAPI(1).then(log)
asyncAPI(2).then(log)
console.log('sync:', 2)
/**
 * sync: 1
 * sync: 2
 * callback: cache
 * callback: update data
 */

实用技巧

此外,类似 setTimeout()Promise.try() 支持 实参转发

javascript 复制代码
setTimeout(function closure() {
  console.log('ES2025')
}, 1_000)

// 👇️ 实参转发
setTimeout(console.log, 1_000, 'ES2025')

// ********************************

Promise.try(function closure() {
  console.log('ES2025')
})

// 👇️ 实参转发
Promise.try(console.log, 'ES2025')

两种写法功能等价,但后者减少了冗余闭包,性能更棒。

浏览器兼容性

2025 年 1 月,Promise.try() 成为 Baseline 基准可用 新特性,所有最新主流浏览器都原生支持。

在尚不支持 Promise.try() 的旧平台中,可以按需引入 polyfill(功能补丁) 优雅降级

以 GitHub 人气最高的 core-js 为例,先用 npm / pnpm 安装 core-js 模块:

bash 复制代码
npm install core-js@latest
# 或者:
pnpm install core-js@latest

然后导入开箱即用的 polyfill,更多细节请参考 core-js 官方文档:

javascript 复制代码
// 集成 polyfill
import 'core-js/es/promise/try.js'

// 基本用法
Promise.try(console.log, 'Hello ES2025')

高潮总结

根据《ECMAScript 语言规范》,es2025 新增 Promise.try() 静态方法,用于调用可能返回 promise 的回调,最终返回 promise。

作为一个更通用的现代 API 糖Promise.try() 无差别执行开发者提供的回调,高效且稳健地开启 Promise 链,更符合人体工程学。

有了 Promise.try(),库作者免于反复造轮子手写样板代码,也避免了错误模拟 Promise.try() 引入潜在 bug,更避免了集成 bluebird 等第三方库增加打包体积。

推荐采用原生 Promise.try(),或集成 polyfill 扩展来重构代码屎山,消除技术负债。

相关推荐
大家的林语冰3 小时前
如何错误手写 ES2025 新增的 Promise.try() 静态方法
前端·javascript·ecmascript 6
繁依Fanyi3 小时前
做一个石头剪刀布小游戏
前端
用户21411832636024 小时前
dify插件开发-Dify 插件如何顺利上架应用市场?流程 + 常见问题一次讲透
前端
繁依Fanyi4 小时前
从零到一,制作一个项目展示平台
前端
给月亮点灯|4 小时前
Vue基础知识-重要的内置关系:vc实例.__proto__.__proto__ === Vue.prototype
前端·vue.js·原型模式
yuehua_zhang5 小时前
uni app 的app 端调用tts 进行文字转语音
前端·javascript·uni-app
再努力"亿"点点5 小时前
炫酷JavaScript鼠标跟随特效
开发语言·前端·javascript
前端达人5 小时前
从 useEffect 解放出来!异步请求 + 缓存刷新 + 数据更新,React Query全搞定
前端·javascript·react.js·缓存·前端框架
qczg_wxg6 小时前
ReactNative系统组件四
javascript·react native·react.js