什么是显式的Promise构建反模式,如何避免呢?


516

我正在编写代码,执行以下操作:

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构造函数反模式 ”,这段代码有什么不好之处,为什么又将其称为反模式


我是否可以确认这样做的目的是(在示例的右侧而不是左侧)删除getStuffDone函数包装并仅使用Promise文字?
Dembinski

1
还是包装中的catchgetStuffDone是反模式?
Dembinski

1
至少对于本机Promise示例,对于.then.catch处理程序,您还具有不必要的函数包装器(即,可能只是.then(resolve).catch(reject)。)完美的反模式风暴。
诺亚·弗雷塔斯

6
@NoahFreitas出于教学目的以这种方式编写了代码。我写这个问题和答案是为了帮助阅读了很多代码的人遇到这个问题:)
Benjamin Gruenbaum

另请参阅stackoverflow.com/questions/57661537/…,以了解如何不仅消除显式的Promise构造,而且还消除全局变量的使用。
David Spector

Answers:


357

Esailija提出的延期反模式(现在是显式构造反模式)是一个新的对诺言做出承诺的普通反模式人,当我第一次使用诺言时,我自己就做出了。上面代码的问题是无法利用承诺链的事实。

承诺可以与之连锁,.then您可以直接返回承诺。您的代码getStuffDone可以重写为:

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

承诺都是关于使异步代码更具可读性,并且在不隐藏该事实的情况下像同步代码一样起作用。承诺表示对一次操作值的抽象,它们抽象出一种编程语言中的语句或表达式的概念。

将API转换为Promise时,仅应使用延迟对象且无法自动执行时,或者在编写以这种方式表示的聚合函数时,。

引用Esailija:

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


@BenjaminGruenbaum:我对为此使用递延方式很有信心,因此不需要新的问题。我只是认为这是您在答案中遗漏的用例。我正在做的事情似乎更像是聚合的反面,不是吗?
mhelvens 2014年

1
@mhelvens如果您要手动将非回调API拆分为一个Promise API,该API符合“将回调API转换为Promise”部分。反模式是无缘无故地将一个诺言包装在另一个诺言中,您没有将诺言包装在开头,因此不适用于此处。
Benjamin Gruenbaum 2014年

@BenjaminGruenbaum:啊,尽管我将递延本身视为反模式,但蓝鸟不赞成使用它们,而您提到的是“将API转换为Promise”(这也是不包装Promise开头的情况)。
mhelvens 2014年

@mhelvens我猜想多余的延迟反模式对于它的实际作用会更准确。蓝鸟(Bluebird)已将.defer()api 弃用到较新的(并抛出安全的)promise构造函数中,但它并没有(绝不反对)构造promise的概念:)
Benjamin Gruenbaum 2014年

1
谢谢@ Roamer-1888,您的参考资料终于帮助我弄清了我的问题。好像我在创建嵌套(未返回)promise却没有意识到。
古罗

134

它出什么问题了?

但是模式有效!

幸运的你。不幸的是,可能不会,因为您可能忘记了一些极端情况。在我所见过的事件中,有一半以上是作者忘记照顾错误处理程序的:

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

如果另一个诺言被拒绝,则将不会引起注意,而不是传播到新的诺言(将在该诺言中进行处理),并且新的诺言将永远挂起,从而导致泄漏。

在您的回调代码导致错误的情况下也会发生相同的事情-例如,当result没有a property并且引发异常时。那将无法处理,并使新的承诺无法实现。

相比之下,using .then()确实会自动处理这两种情况,并在发生错误时拒绝新的承诺:

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

延迟的反模式不仅麻烦,而且容易出错。使用.then()的链接是安全得多。

但是我已经处理了一切!

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

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

如何避免呢?

因此,每当您发现自己手动创建PromiseDeferred且已经存在现有的Promise时,请先检查库API。Deferred反模式经常被那些仅将诺言视为观察者模式的人使用,但是诺言更多比回调:他们应该是组合的。每个体面的图书馆都有许多易于使用的功能,以各种可想而知的方式来构成承诺,可以处理您不想处理的所有低级内容。

如果您发现有需要以现有助手功能不支持的新方式撰写一些承诺,那么最后一个选择就是使用不可避免的Deferreds编写自己的函数。考虑切换到功能更强大的库,和/或针对当前库提出错误。它的维护者应该能够从现有功能中获取组成,为您实现新的帮助器功能和/或帮助确定需要处理的边缘情况。


除了函数之外,是否有其他示例setTimeout可以使用构造函数,但不能视为“ Promise构造函数anitpattern”?
guest271314 2015年

1
@ guest271314:所有异步操作都不会返回承诺。尽管经常,使用库的专用promisification帮助器可以获得更好的结果。并且请确保始终在最低级别进行承诺,因此它不是“ 包含setTimeout的函数setTimeout,而是“ 函数本身 ”。
Bergi 2015年

“并确保始终在最低级别进行承诺,因此它不是“包含setTimeout”的功能,而是“setTimeout本身”
guest271314 2015年

@ guest271314仅包含调用的函数setTimeout该函数明显不同setTimeout本身,不是吗?
Bergi 2015年

4
我认为此处的重要课程之一(到目前为止尚未明确说明)是Promise及其链接的“ then”表示一个异步操作:初始操作在Promise构造函数中,而最终端点在“然后”功能。因此,如果先执行同步操作,再执行异步操作,则将同步内容放入Promise中。如果您先执行异步操作,然后再进行同步,则将同步内容放入“然后”。在第一种情况下,返回原始的Promise。在第二种情况下,返回Promise / then链(也是Promise)。
David Spector
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.