What is the explicit promise construction antipattern and how do I avoid it?
我编写的代码看起来像:
1 2 3 4 5 6 7 8 9 10 11
| 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文字?
-
或者在反模式的getStuffDone包装器中有catch块?
-
至少对于本机Promise示例,您还有.then和.catch处理程序的不必要的函数包装器(即它可能只是.then(resolve).catch(reject)。)一个完美的反模式风暴。
-
@NoahFreitas该代码以教学目的编写。 我写了这个问题和答案,以帮助那些在阅读了大量代码之后遇到这个问题的人们:)
-
另见stackoverflow.com/questions/57661537/… 如何消除不仅显式Promise构造,而且还使用全局变量。
Esailija创造的延迟反模式(现在是明确构建的反模式)是一种常见的反模式人物,他们是新的承诺,我在我第一次使用承诺时自己创造了。上述代码的问题在于无法利用promises链的事实。
Promise可以与.then链接,你可以直接返回promises。您在getStuffDone中的代码可以重写为:
1 2 3
| function getStuffDone(param){
return myPromiseFn(param+1); // much nicer, right?
} |
Promise都是为了使异步代码更具可读性,并且表现得像同步代码而不隐藏这一事实。 Promise表示对一次操作的值的抽象,它们在编程语言中抽象出语句或表达式的概念。
在将API转换为promises并且无法自动执行时,或者当您编写更容易表达的聚合函数时,您应该只使用延迟对象。
引用Esailija:
This is the most common anti-pattern. It is easy to fall into this when you don't really understand promises and think of them as glorified event emitters or callback utility. Let's recap: promises are about making asynchronous code retain most of the lost properties of synchronous code such as flat indentation and one exception channel.
-
AKA"被遗忘的承诺"在这里
-
@BenjaminGruenbaum:我对此使用deferreds很有信心,所以不需要一个新问题。我只是认为这是你在答案中遗漏的一个用例。我正在做的事情似乎更像是聚合的反面,不是吗?
-
@mhelvens如果您手动将非回调API拆分为适合"将回调API转换为承诺"部分的promise API。反模式是关于在没有充分理由的情况下将承诺包含在另一个承诺中,你没有包含一个承诺,所以它不适用于此。
-
@BenjaminGruenbaum:啊,我虽然推迟了自己被认为是反模式,蓝鸟贬低它们,你提到"将API转换为承诺"(这也是一个未包含承诺开始的情况)。
-
@mhelvens我想多余的延迟反模式对于实际做的更准确。 Bluebird将.defer() api弃用到较新的(并抛出安全)promise构造函数中,它没有(绝不)弃用构造promise的概念:)
-
@BenjaminGruenbaum:不,当然他们并没有弃用新承诺的创建。但正如你所说,他们弃用了.defer() API。在我看来,这是一个糟糕的举动,因为'defer -like'API更灵活,并且对于像我这样的用例来说显然是必需的。 ---当然,这不是真正的问题。他们自己的文档解释了如何使用promise构造函数表达defer()。
-
一个问题:如果我有两个场景,如果值存在则返回值else make async call并获取它。我通过将整个事物包装在promise中并用数据解析promise来实现它,否则从异步调用中返回promise。有没有更好的办法?
-
@Maverick你可以在getStuffDone里面Promise.resolve(42)返回现有的值,或者更好地使用Promise.resolve(getStuffDone(666)).then(...).catch(...)而不是getStuffDone(666).then(...)来确保你使用Promise,即使getStuffDone不够合作(比如抛出一个) TypeError) - 请参阅You-Dont-Know-JS#trustable-promise
-
谢谢你@ Roamer-1888你的参考帮助我终于搞清楚我的问题是什么。看起来我在没有意识到的情况下创建了嵌套(未回复)的承诺。
它出什么问题了?
But the pattern works!
幸运的你。不幸的是,它可能没有,因为你可能忘记了一些边缘情况。在我看过的一半以上的事件中,作者忘记了处理错误处理程序:
1 2 3 4 5
| return new Promise(function(resolve) {
getOtherPromise().then(function(result) {
resolve(result.property.example);
});
}) |
如果另一个承诺被拒绝,这将被忽视,而不是传播到新的承诺(它将被处理) - 并且新承诺永远保持未决,这可能导致泄漏。
在你的回调代码导致错误的情况下会发生同样的事情 - 例如当result没有property并抛出异常时。那将是未经处理的,并且未能解决新的承诺。
相反,使用.then()会自动处理这两种情况,并在发生错误时拒绝新的承诺:
1 2 3
| return getOtherPromise().then(function(result) {
return result.property.example;
}) |
延迟反模式不仅麻烦,而且容易出错。使用.then()进行链接更加安全。
But I've handled everything!
真?好。但是,这将非常详细和丰富,特别是如果您使用支持其他功能(如取消或消息传递)的promise库。或者也许它将来,或者你想要将你的图书馆换成更好的图书馆?您不希望为此重写代码。
库的方法(then)不仅本身支持所有功能,它们也可能具有某些优化。使用它们可能会使您的代码更快,或者至少可以通过库的未来版本进行优化。
我该如何避免呢?
因此,每当您发现自己手动创建Promise或Deferred并且涉及现有的承诺时,请首先检查库API。延迟反模式通常由那些将promises [仅]视为观察者模式的人应用 - 但承诺不仅仅是回调:它们应该是可组合的。每个体面的图书馆都有许多易于使用的功能,以各种可想象的方式构成承诺,照顾你不想处理的所有低级别的东西。
如果您发现需要以现有辅助函数不支持的新方式编写某些promise,那么使用不可避免的Deferreds编写自己的函数应该是最后一个选项。考虑切换到功能更强大的库,和/或针对当前库提交错误。它的维护者应该能够从现有函数派生组合,为您实现一个新的辅助函数和/或帮助识别需要处理的边缘情况。
-
除了包含setTimeout的函数之外,还有其他示例,其中可以使用构造函数但不能将其视为"Promise构造函数anitpattern"吗?
-
@ guest271314:异步的所有异步都没有返回一个promise。虽然经常足够你使用图书馆的专职宣传助手来获得更好的结果。并确保始终在最低级别进行promisify,因此它不是"包含setTimeout的函数",而是"函数setTimeout本身"。
-
"并确保始终在最低级别进行宣传,因此它不是"包含setTimeout的函数",而是"函数setTimeout本身"可以描述,链接到两者之间的差异?
-
@ guest271314只包含对setTimeout的调用的函数明显不同于函数setTimeout本身,不是吗?
-
也许这里并没有意识到明显的区别。 function () {setTimeout(dostuff, duration)},setTimeout(dostuff, duration)?可以描述两者之间的上下文差异,如果上面两个变化是或者不是,"对setTimeout的调用明显不同于函数setTimeout"的准确表示吗?
-
@ guest271314:要宣传的最低级别是setTimeout功能。你的doStuff应该进入一个承诺回调。
-
我在规范中找不到任何Promise.prototype.done。你指的是非标准的promisejs.org/api/#Promise_prototype_done,还是指的是then?
-
@Oriol:是的,Q或jQuery的done或其他什么。 无论如何反自我的实例并不重要,但我会把它换成标准的(假设我也使用了promise构造函数)
-
我认为这里有一个重要的教训,到目前为止还没有明确说明,是Promise及其链接'then'代表一个异步操作:初始操作在Promise构造函数中,最终端点在' 那么'功能。 因此,如果您有一个同步操作,然后执行异步操作,请将同步内容放入Promise中。 如果您有异步操作后跟同步,请将同步内容放在'then'中。 在第一种情况下,返回原始的Promise。 在第二种情况下,返回Promise / then链(也是Promise)。