什么是将同步函数包装成Promise的最佳方式?

18

假设我有一个同步函数,比如path.join()。我想把它包装成一个Promise,因为我希望异常能够在catch()块中处理。如果按照以下方式进行包装,则无法在Promise.catch()块中捕获异常。因此,我必须使用if来检查返回值是否为错误,然后调用resolvereject函数。你们有其他解决方案吗?

var joinPaths = function(path1,path2) {
  return new promise(function (resolve, reject) {
    resolve(path.join(path1, path2));
  });
};

2
你为什么想要将同步函数包装成一个Promise? - Rodrigo Juarez
2
单元测试是一个常见的情况,当需要用硬编码值替换一些异步调用时。 - Alexei Levenkov
1
将同步函数包装成Promise的最佳方法是不要这样做。 - Bergi
3
可以使用 Promise.resolve().then(()=> path.join(path1, path2)).…来实现。 - Bergi
@RodrigoJuarez 如果我在 then() 中使用 path.join,那么我可以在 catch 块中处理所有异常。 - s1n7ax
3个回答

20

不清楚为什么会将同步操作包装在一个Promise中,这只会使其更难以使用,因为同步操作已经可以在Promise链中正常使用。

我所见过的唯一两种有用的情况是,将同步操作转换成Promise来启动一个后续操作是异步的Promise链,或者当分支的结果既有异步Promise又有同步操作时。 在这种情况下,您需要在两种情况下都返回Promise,以便调用者具有一致的异步接口,无论采取哪个分支。或者,这也可能发生在共同API的不同实现中,其中一种实现是同步的,而另一种是异步的。该API将被设计为具有异步接口,同步实现可能会将自身包装在Promise中,以满足共同API的异步契约。

除此之外,您通常不应将同步操作变成异步操作,因为它只会不必要地增加使用的复杂性。

我所知道的最简单的方法是将其转换为Promise:

Promise.resolve(path.join(path1, path2)).then(function(path) {
   // use the result here
});

根据您的评论,在.then()处理程序内部,异常已经被Promise基础设施捕获并转化为一个拒绝的promise。因此,如果您有以下代码:

someAsyncOp().then(function(value) {
   // some other stuff
   // something that causes an exception
   throw new Error("timeout");
}).catch(function(err){
   console.log(err);   // will show timeout
});

那么,该异常已经被映射成一个拒绝的承诺。当然,如果你想在.then()处理程序内部处理异常(而不是将承诺变成拒绝),那么你可以像捕获其他同步代码的本地异常一样使用传统的try/catch包裹同步操作以捕获异常。但是,如果你希望承诺在.then()处理程序内部有异常时拒绝,那么这一切都会自动完成(这是Promise非常好的一个特性)。


如果我在 Promise 链中使用 path.join,并且我想在 Promise 的 .catch() 块中处理异常,那么如何在不将其包装成 Promise 的情况下实现呢? - s1n7ax
3
像往常一样,如果您将实际的代码和实际的问题放到问题中,我们就可以更好地帮助您。我不完全明白您试图做什么。在Promise链中的.then()处理程序已经被包装在try/catch中,任何在.then()处理程序中抛出的异常都会自动拒绝Promise链。您不必在.then()处理程序内执行任何操作以获得该功能。您还可以使用try/catch自己捕获同步异常并在本地处理它。 - jfriend00
7
将同步函数封装在Promise中的原因之一是为了遵循一个约定,即if (needsUpdating) { return object.asyncUpdate(); } else { return object };。如果其中一条路径返回一个Promise,则另一条路径也应该返回一个Promise,即使它不运行任何异步代码。 - Daniel T.
我认为这与@DanielT.提到的类似,但我认为它可能有用于创建一个API,允许您交换不同的供应商实现。我目前正在制作一个应用程序的演示版本,其中真正的应用程序使用Firestore,但在演示版本中我将使用localStorage。即使localStorage是同步的,我也想用promises来包装以使事情更加透明。这听起来像个坏主意吗? - ckot
@ckot - 是的,一个有时会是异步的API,即使在某些情况下实现不是异步的,也必须始终提供异步API。这将是将一些同步代码包装在Promise中的有效/有用原因。 - jfriend00
@jfriend00 谢谢。很高兴知道我这样做不会引入代码气味。 - ckot

9

我不确定这是否是最佳方法,但它可行。我来到StackOverflow检查是否有更好的方法。

new Promise((resolve, reject) => {
    try {
        resolve(path.join());
    } catch (err) {
        reject(err);
    }
})

4
非常快速的方法是在函数前加上async关键字。任何异步函数都会返回一个Promise。
const sum = async (a, b) => a + b;

sum(1, 2).then(value => console.log(value))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接