什么是显式承诺构造函数反模式,我该如何避免它?

时间:2014-05-22 10:07:01

标签: javascript promise q bluebird es6-promise

我编写的代码看起来像是:

function getStuffDone(param) {           | function getStuffDone(param) {
    var d = Q.defer(); /* or $q.defer */ |     return new Promise(function(resolve, reject) {
    // or = new $.Deferred() etc.        |     // using a promise constructor
    myPromiseFn(param+1)                 |         myPromiseFn(param+1)
    .then(function(val) { /* or .done */ |         .then(function(val) {
        d.resolve(val);                  |             resolve(val);
    }).catch(function(err) { /* .fail */ |         }).catch(function(err) {
        d.reject(err);                   |             reject(err);
    });                                  |         });
    return d.promise; /* or promise() */ |     });
}                                        | }

有人告诉我,这分别称为“延迟反模式”或“ Promise构造函数反模式”,这段代码有什么不好,为什么会这样叫antipattern

3 个答案:

答案 0 :(得分:307)

deferred antipattern (now explicit-construction anti-pattern)创造的Esailija是一个常见的反模式的人,他们是承诺的新手,我在我第一次使用承诺时就已经成功了。上述代码的问题在于无法利用promises chain的事实。

Promise可以与.then链接,您可以直接返回承诺。您在getStuffDone中的代码可以重写为:

function getStuffDone(param){
    return myPromiseFn(param+1); // much nicer, right?
}

Promise就是让异步代码更具可读性,并且表现得像同步代码,而不会隐藏这一事实。 Promise表示对一次操作值的抽象,它们用编程语言抽象语句或表达式的概念。

您应该只在converting an API to promises时使用延迟对象,并且不能自动执行,或者在编写更容易用这种方式表达的聚合函数时。

引用Esailija:

  

这是最常见的反模式。当你不真正理解承诺并将它们视为美化事件发射器或回调实用程序时,很容易陷入这种情况。让我们回顾一下:promises是关于使异步代码保留同步代码的大部分丢失属性,例如扁平缩进和一个异常通道。

答案 1 :(得分:118)

它出了什么问题?

  

但模式有效!

幸运的是你。不幸的是,它可能没有,因为你可能忘记了一些边缘情况。在我看过的一半以上的事件中,作者忘记了处理错误处理程序:

return new Promise(function(resolve) {
    getOtherPromise().then(function(result) {
        resolve(result.property.example);
    });
})

如果另一个承诺被拒绝,这将被忽视,而不是传播到新的承诺(它将被处理) - 并且新的承诺永远待决,这可能导致泄漏。

在你的回调代码导致错误的情况下会发生同样的事情 - 例如当result没有property并且抛出异常时。那将是未经处理的,并且未能解决新的承诺。

相反,使用.then()会自动处理这两种情况,并在发生错误时拒绝新的承诺:

 return getOtherPromise().then(function(result) {
     return result.property.example;
 })

延迟反模式不仅麻烦,而且容易出错。使用.then()进行链接会更加安全。

  

但我已经处理了所有事情!

真的?好。但是,这将非常详细和丰富,特别是如果您使用支持其他功能(如取消或消息传递)的promise库。或者也许它将来,或者你想要将你的图书馆换成更好的图书馆?您不希望为此重写代码。

库的方法(then)不仅本身支持所有功能,还可能具有某些优化功能。使用它们可能会使您的代码更快,或者至少可以通过库的未来版本进行优化。

我该如何避免?

因此,每当您发现自己手动创建PromiseDeferred且涉及现有的承诺时,首先检查库API 。延迟反模式通常由那些将promises [仅]视为观察者模式的人应用 - 但是promises are more than callbacks:它们应该是可组合的。每个体面的图书馆都有许多易于使用的功能,以各种可想象的方式构成承诺,照顾你不想处理的所有低级别的东西。

如果您发现需要以现有帮助函数不支持的新方式编写某些promise,那么使用不可避免的Deferreds编写自己的函数应该是最后一个选项。考虑切换到功能更强大的库,和/或针对当前库提交错误。它的维护者应该能够从现有函数派生组合,为你实现一个新的辅助函数和/或帮助识别需要处理的边缘情况。

答案 2 :(得分:10)

现在 7 年后,这个问题有了更简单的答案:

如何避免显式构造函数反模式?

使用 async function,然后使用 await 每个 Promise!

而不是像这样手动构建嵌套的 Promise 链:

function promised() {
  return new Promise(function(resolve) {
    getOtherPromise().then(function(result) {
      getAnotherPromise(result).then(function(result2) {
        resolve(result2);
      });
    });
  });
}

只需打开您的函数 async 并使用 await 关键字停止函数的执行,直到 Promise 解决:

async function promised() {
   const result =  await getOtherPromise();
   const result2 = await getAnotherPromise(result);
   return result2;
}

这有很多好处:

  • 调用 async 函数总是返回一个 Promise,它使用返回的值进行解析,如果在异步函数中抛出错误则拒绝
  • 如果一个 awaited Promise 拒绝,错误会被抛出到异步函数中,所以你可以像同步错误一样try { ... } catch(error) { ... }
  • 您可以在循环和 if 分支内await,使大部分 Promise 链逻辑变得微不足道
  • 尽管异步函数的行为大多类似于 Promise 链,但它们更易于阅读(也更易于推理)

我如何await回调?

如果回调只回调一次,并且您正在调用的 API 没有提供 Promise(大多数都提供!)这是使用 Promise 构造函数的唯一原因: >

 // Create a wrapper around the "old" function taking a callback, passing the 'resolve' function as callback
 const delay = time => new Promise((resolve, reject) =>
   setTimeout(resolve, time)
 ); 

 await delay(1000);

如果 await 停止执行,调用 async function 是否直接返回结果?

没有。如果您调用异步函数,则始终会返回 Promise。然后,您也可以在异步函数中 await 那个 Promise。您不能在同步函数内等待结果(您必须调用 .then 并附加回调)。

从概念上讲,同步 function 总是在一个作业中运行到完成,而 async function 同步运行直到它们到达 await,然后它们会在另一个作业中继续。