- Hey, 我是 沉浸式趣談
- 本文首發於【沉浸式趣談】,我的個人博客 https://yaolifeng.com 也同步更新。
- 轉載請在文章開頭註明出處和版權信息。
- 如果本文對您有所幫助,請 點贊、評論、轉發,支持一下,謝謝!
聊到異步,Promise 大家肯定都不陌生,是咱們處理異步操作的神器
不過呢,就算有 Promise,有時候處理一些既可能是同步又可能是異步的函數,或者那種隨時可能在啓動時就給你扔個同步錯誤的函數,還是有點小別扭。
你懂的,就是那種“我想用 .then().catch() 一把梭,但又怕它在 Promise 鏈開始前就崩了”的尷尬。
好消息來了!
ES2025 憋了個大招 —— Promise.try()。
Promise.try() 到底是何方神聖?
説白了,它就是 Promise 上的一個靜態方法,像個萬能啓動器。
你扔給它一個函數(管它是同步的、異步的,會返回值還是會拋錯),它都能穩穩地給你包成一個 Promise。
代碼大概長這樣:
Promise.try(你要運行的函數, ...可能需要的參數);
簡單粗暴,對吧?
關鍵在於,它特別擅長處理那些“不確定性”。
比如:
- 如果你的函數是同步的,執行完直接返回值
X?那Promise.try()就給你一個resolved狀態、值為X的 Promise。 - 要是函數同步執行時直接
throw new Error()了呢?(這種最頭疼了,以前可能直接崩掉後續代碼)Promise.try()會捕獲這個錯誤,然後給你一個rejected狀態的 Promise,錯誤就在裏面,你可以用.catch()接住。簡直完美! - 那如果函數本身就返回一個異步的 Promise 呢?沒問題,
Promise.try()就直接用那個 Promise 的狀態。
為啥我們需要這玩意兒?以前不也活得好好的?
嗯... 活得好是好,但可能不夠優雅,或者説,不夠省心。
記得以前咱們想統一處理同步/異步函數時,可能會用 Promise.resolve().then(func) 這招嗎?
const f = () => console.log('我應該立刻執行!');
Promise.resolve().then(f); // 但它被塞到微任務隊列裏去了
console.log('我先執行了...');
// 輸出:
// 我先執行了...
// 我應該立刻執行!
明明 f 是個同步函數,結果被 then 這麼一搞,硬生生變成了異步執行。
有時候我們並不想要這種延遲。而且,如果 f 本身在執行前就拋錯,Promise.resolve() 可管不了。
Promise.try() 就是來解決這個痛點的。
它能讓你的函數(如果是同步的)基本上是立即嘗試執行,同時還保證了無論如何你都能拿到一個 Promise,並且同步錯誤也能被鏈式捕獲。
...呃,或者更準確地説,它提供了一個統一的、更安全的 Promise 啓動方式。
來,上代碼感受下
- 搞定同步函數:
const syncTask = () => {
console.log('同步任務跑起來~');
return '同步搞定';
};
Promise.try(syncTask)
.then(res => console.log('結果:', res)) // 立刻輸出 "同步搞定"
.catch(err => console.error('出錯了?', err));
// 控制枱會先打印 "同步任務跑起來~",然後是 "結果: 同步搞定"
- 處理異步函數(這個沒啥特別,就是正常用):
const asyncTask = () => new Promise(resolve => setTimeout(() => resolve('異步也 OK'), 500));
Promise.try(asyncTask)
.then(res => console.log('結果:', res)) // 大約 500ms 後輸出 "異步也 OK"
.catch(err => console.error('出錯了?', err));
- 最妙的地方:捕獲同步錯誤
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 塊了。(這點我個人覺得超級實用!)
這東西有啥好的?總結一下哈:
- 入口統一: 不管三七二十一,同步異步函數塞進去,出來的都是 Promise,後續處理邏輯可以寫得非常一致。代碼看着就清爽多了。
- 同步錯誤保險: 這是重點!能捕獲啓動函數時的同步錯誤,塞到 Promise 鏈裏,讓你用
.catch()一勺燴了。避免了裸露的try...catch或者漏抓錯誤的風險。 - 可讀性提升: 意圖更明顯,一看
Promise.try()就知道這裏是安全啓動一個可能同步也可能異步的操作。
實戰中能怎麼玩?
-
調 API:
fetch本身返回 Promise,但之前的 URL 處理、參數構造啥的可能是同步的,萬一出錯呢?- 用
Promise.try(() => fetch(buildUrl(params)))就很穩。
// 以前的寫法 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)))串起來就很自然。// 假設我們有個處理用户輸入的場景 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包裹一下,可以簡化錯誤處理邏輯。(當然,具體庫可能有自己的最佳實踐,這只是個思路)// 假設我們有個文件操作庫,它的 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 的使用,尤其是在鏈條的起點上,變得更健壯、更統一。
我個人感覺,一旦大家習慣了它帶來的便利(特別是那個同步錯誤捕獲!),估計很快就會成為咱們工具箱裏的常客了。
有機會的話,真得試試看!
其他好文推薦
搞定 XLSX 預覽?別瞎找了,這幾個庫(尤其最後一個)真香!
實戰分享】10 大支付平台全方面分析,獨立開發必備!
關於 MCP,這幾個網站你一定要知道!
做 Docx 預覽,一定要做這個神庫!!
【完整彙總】近 5 年 JavaScript 新特性完整總覽
關於 Node,一定要學這個 10+萬 Star 項目!