ES2025 `Promise.try()` 新API 太好用

笔记哥 / 04-28 / 46点赞 / 0评论 / 901阅读
## ES2025 憋了个大招 —— `Promise.try()`。 ### **`Promise.try()` 到底是何方神圣?** 说白了,它就是 `Promise` 上的一个静态方法,像个万能启动器。 你扔给它一个函数(管它是同步的、异步的,会返回值还是会抛错),它都能稳稳地给你包成一个 Promise。 代码大概长这样: ```javascript Promise.try(你要运行的函数, ...可能需要的参数); ``` 简单粗暴,对吧? 关键在于,它特别擅长处理那些“不确定性”。 比如: - 如果你的函数是**同步**的,执行完直接返回值 `X`?那 `Promise.try()` 就给你一个 `resolved` 状态、值为 `X` 的 Promise。 - 要是函数**同步**执行时直接 `throw new Error()` 了呢?(这种最头疼了,以前可能直接崩掉后续代码)`Promise.try()` 会捕获这个错误,然后给你一个 `rejected` 状态的 Promise,错误就在里面,你可以用 `.catch()` 接住。简直完美! - 那如果函数本身就返回一个**异步**的 Promise 呢?没问题,`Promise.try()` 就直接用那个 Promise 的状态。 **为啥我们需要这玩意儿?以前不也活得好好的?** 嗯... 活得好是好,但可能不够优雅,或者说,不够省心。 记得以前咱们想统一处理同步/异步函数时,可能会用 `Promise.resolve().then(func)` 这招吗? ```javascript const f = () => console.log('我应该立刻执行!'); Promise.resolve().then(f); // 但它被塞到微任务队列里去了 console.log('我先执行了...'); // 输出: // 我先执行了... // 我应该立刻执行! ``` 明明 `f` 是个同步函数,结果被 `then` 这么一搞,硬生生变成了异步执行。 有时候我们并不想要这种延迟。而且,如果 `f` 本身在执行前就抛错,`Promise.resolve()` 可管不了。 `Promise.try()` 就是来解决这个痛点的。 它能让你的函数(如果是同步的)基本上是立即尝试执行,同时还保证了无论如何你都能拿到一个 Promise,并且同步错误也能被链式捕获。 ...呃,或者更准确地说,它提供了一个统一的、更安全的 Promise 启动方式。 ### **来,上代码感受下** - **搞定同步函数:** ```javascript const syncTask = () => { console.log('同步任务跑起来~'); return '同步搞定'; }; Promise.try(syncTask) .then(res => console.log('结果:', res)) // 立刻输出 "同步搞定" .catch(err => console.error('出错了?', err)); // 控制台会先打印 "同步任务跑起来~",然后是 "结果: 同步搞定" ``` - **处理异步函数(这个没啥特别,就是正常用):** ```javascript const asyncTask = () => new Promise(resolve => setTimeout(() => resolve('异步也 OK'), 500)); Promise.try(asyncTask) .then(res => console.log('结果:', res)) // 大约 500ms 后输出 "异步也 OK" .catch(err => console.error('出错了?', err)); ``` - **最妙的地方:捕获同步错误** ```javascript const potentiallyExplodingTask = () => { if (Math.random() < 0.5) { // 假设这里有 50% 概率直接炸 throw new Error('Boom! 同步错误'); } return '安全通过'; }; // 多试几次你就能看到效果 Promise.try(potentiallyExplodingTask) .then(res => console.log('这次运气不错:', res)) .catch(err => console.error('捕获到错误:', err.message)); // 能抓住那个 "Boom!" ``` 就算 `potentiallyExplodingTask` 在 `Promise` 链条“正式”开始前就同步抛错了,`Promise.try()` 也能稳稳接住,交给你后面的 `.catch()` 处理。 这在以前,可能就直接导致程序崩溃或者需要写额外的 `try...catch` 块了。(这点我个人觉得超级实用!) ### **这东西有啥好的?总结一下哈:** 1. **入口统一:** 不管三七二十一,同步异步函数塞进去,出来的都是 Promise,后续处理逻辑可以写得非常一致。代码看着就清爽多了。 2. **同步错误保险:** 这是重点!能捕获启动函数时的同步错误,塞到 Promise 链里,让你用 `.catch()` 一勺烩了。避免了裸露的 `try...catch` 或者漏抓错误的风险。 3. **可读性提升:** 意图更明显,一看 `Promise.try()` 就知道这里是安全启动一个可能同步也可能异步的操作。 ### **实战中能怎么玩?** - **调 API:** `fetch` 本身返回 Promise,但之前的 URL 处理、参数构造啥的可能是同步的,万一出错呢? - 用 `Promise.try(() => fetch(buildUrl(params)))` 就很稳。 ```javascript // 以前的写法 function fetchUserData(userId) { try { // 这里的 buildApiUrl 可能会同步抛出错误 const url = buildApiUrl(`/users/${userId}`); return fetch(url).then(res => res.json()); } catch (err) { return Promise.reject(err); // 手动转换成 Promise 错误 } } // 使用 Promise.try 的写法 function fetchUserData(userId) { return Promise.try(() => { const url = buildApiUrl(`/users/${userId}`); return fetch(url).then(res => res.json()); }); } // 调用示例 fetchUserData('123') .then(data => console.log('用户数据:', data)) .catch(err => console.error('获取用户数据失败:', err)); ``` - **混合任务链:** 比如先跑个同步任务,再根据结果跑个异步任务,用 `Promise.try(syncTask).then(res => Promise.try(() => asyncTask(res)))` 串起来就很自然。 ```javascript // 假设我们有个处理用户输入的场景 function validateInput(input) { // 同步验证,可能会抛出错误 if (!input || input.length < 3) { throw new Error('输入太短了!'); } return input.trim().toLowerCase(); } function saveToDatabase(processedInput) { // 异步保存,返回 Promise return new Promise((resolve, reject) => { setTimeout(() => { if (processedInput === 'admin') { reject(new Error('不能使用保留关键字')); } else { resolve({ success: true, id: Date.now() }); } }, 500); }); } // 使用 Promise.try 组合这两个任务 function processUserInput(rawInput) { return Promise.try(() => validateInput(rawInput)).then(validInput => Promise.try(() => saveToDatabase(validInput)) ); } // 测试不同情况 processUserInput('') // 同步错误 .then(result => console.log('保存成功:', result)) .catch(err => console.error('处理失败:', err.message)); processUserInput('admin') // 异步错误 .then(result => console.log('保存成功:', result)) .catch(err => console.error('处理失败:', err.message)); processUserInput('user123') // 成功情况 .then(result => console.log('保存成功:', result)) .catch(err => console.error('处理失败:', err.message)); ``` - **数据库/文件操作:** 很多库的 API 设计可能五花八门,有的同步出错有的异步出错,用 `Promise.try` 包裹一下,可以简化错误处理逻辑。(当然,具体库可能有自己的最佳实践,这只是个思路) ```javascript // 假设我们有个文件操作库,它的 API 设计有点混乱 const fileOps = { readConfig(path) { // 这个方法可能同步抛错(比如路径格式不对) // 也可能返回 Promise(实际读取文件时) if (!path.endsWith('.json')) { throw new Error('配置文件必须是 JSON 格式'); } return new Promise((resolve, reject) => { setTimeout(() => { if (path.includes('nonexistent')) { reject(new Error('文件不存在')); } else { resolve({ version: '1.0', settings: { theme: 'dark' } }); } }, 100); }); }, }; // 不使用 Promise.try 的话,调用方需要自己处理同步错误 function loadAppConfig_old(configPath) { try { return fileOps.readConfig(configPath).then(config => { console.log('配置加载成功'); return config; }); } catch (err) { console.error('同步错误:', err); return Promise.reject(err); } } // 使用 Promise.try,代码更简洁,错误处理更统一 function loadAppConfig(configPath) { return Promise.try(() => fileOps.readConfig(configPath)).then(config => { console.log('配置加载成功'); return config; }); } // 测试各种情况 loadAppConfig('settings.txt') // 同步错误 - 非 JSON 文件 .catch(err => console.error('加载失败:', err.message)); loadAppConfig('nonexistent.json') // 异步错误 - 文件不存在 .catch(err => console.error('加载失败:', err.message)); loadAppConfig('settings.json') // 成功情况 .then(config => console.log('配置内容:', config)) .catch(err => console.error('加载失败:', err.message)); ``` **聊了这么多,总而言之...** `Promise.try()` 这哥们儿,虽然看起来只是个小补充,但它解决的痛点可是实实在在的。 它让 Promise 的使用,尤其是在链条的起点上,变得更健壮、更统一。