聊到异步,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))
.catch(err => console.error('出错了?', err));
const potentiallyExplodingTask = () => {
if (Math.random() < 0.5) {
throw new Error('Boom! 同步错误');
}
return '安全通过';
};
Promise.try(potentiallyExplodingTask)
.then(res => console.log('这次运气不错:', res))
.catch(err => console.error('捕获到错误:', err.message));
就算 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 {
const url = buildApiUrl(`/users/${userId}`);
return fetch(url).then(res => res.json());
} catch (err) {
return Promise.reject(err);
}
}
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) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (processedInput === 'admin') {
reject(new Error('不能使用保留关键字'));
} else {
resolve({ success: true, id: Date.now() });
}
}, 500);
});
}
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
包裹一下,可以简化错误处理逻辑。(当然,具体库可能有自己的最佳实践,这只是个思路)
const fileOps = {
readConfig(path) {
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);
});
},
};
function loadAppConfig_old(configPath) {
try {
return fileOps.readConfig(configPath).then(config => {
console.log('配置加载成功');
return config;
});
} catch (err) {
console.error('同步错误:', err);
return Promise.reject(err);
}
}
function loadAppConfig(configPath) {
return Promise.try(() => fileOps.readConfig(configPath)).then(config => {
console.log('配置加载成功');
return config;
});
}
loadAppConfig('settings.txt')
.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 的使用,尤其是在链条的起点上,变得更健壮、更统一。
我个人感觉,一旦大家习惯了它带来的便利(特别是那个同步错误捕获!),估计很快就会成为咱们工具箱里的常客了。
有机会的话,真得试试看!
转自https://www.cnblogs.com/leadingcode/p/18851970
该文章在 2025/5/7 9:09:40 编辑过