提问人:Benjamin Gruenbaum 提问时间:5/22/2014 最后编辑:Benjamin Gruenbaum 更新时间:10/7/2023 访问量:136200
什么是显式承诺构造反模式,如何避免它?
What is the explicit promise construction antipattern and how do I avoid it?
问:
我正在编写代码,该代码看起来像:
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
构造函数反模式”,这段代码有什么不好,为什么这被称为反模式?
答:
Esailija 创造的延迟反模式(现在是显式构造反模式)是一种常见的反模式,不熟悉 promise 的人,我第一次使用 promise 时就自己做了。上述代码的问题在于未能利用承诺链的事实。
Promise 可以链接,您可以直接返回 Promise。您的代码可以重写为:.then
getStuffDone
function getStuffDone(param){
return myPromiseFn(param+1); // much nicer, right?
}
Promise 就是要使异步代码更具可读性,并且行为类似于同步代码,而不会隐藏这一事实。Promise 表示对一次性操作值的抽象,它们抽象了编程语言中语句或表达式的概念。
仅当将 API 转换为 promise 且无法自动执行此操作时,或者编写以这种方式更容易表达的聚合函数时,才应使用延迟对象。
引用 Esailija 的话:
这是最常见的反模式。当你不真正理解承诺,并将它们视为美化的事件发射器或回调实用程序时,很容易陷入这种情况。让我们回顾一下:promise 是关于使异步代码保留同步代码的大部分丢失属性,例如平面缩进和一个异常通道。
评论
.defer()
这是怎么回事?
但这种模式有效!
幸运的你。不幸的是,它可能不会,因为你可能忘记了一些边缘情况。在我见过的超过一半的事件中,作者忘记了处理错误处理程序:
function bad() {
return new Promise(function(resolve) {
getOtherPromise().then(function(result) {
resolve(result.property.example);
});
})
}
如果另一个承诺被拒绝,这将在不被注意的情况下发生,而不是传播到新承诺(它将被处理的地方)——并且新承诺将永远处于待处理状态,这可能会导致泄漏。
如果你的回调代码导致错误,也会发生同样的事情 - 例如,当没有 a 时,会抛出异常。这将得不到处理,使新的承诺得不到解决。result
property
相比之下,using 会自动处理这两种情况,并在发生错误时拒绝新 promise:.then()
function good() {
return getOtherPromise().then(function(result) {
return result.property.example;
})
}
延迟反模式不仅繁琐,而且容易出错。用于链接要安全得多。.then()
但我已经处理了一切!
真?好。但是,这将是非常详细和丰富的,特别是如果您使用支持其他功能(如取消或消息传递)的 promise 库。或者也许将来会,或者您想将您的库换成更好的库?你不会想为此重写你的代码。
库的方法 () 不仅原生支持所有功能,而且可能还进行了某些优化。使用它们可能会使您的代码更快,或者至少允许通过库的未来修订进行优化。then
我该如何避免它?
因此,每当您发现自己手动创建 OR 并且涉及已经存在的承诺时,请先检查库 API。延迟反模式通常被那些将 promise [仅] 视为观察者模式的人应用——但 promise 不仅仅是回调:它们应该是可组合的。每个像样的库都有很多易于使用的功能,用于以各种可以想象的方式组合承诺,处理所有你不想处理的低级内容。Promise
Deferred
如果您发现需要以现有帮助程序函数不支持的新方式编写一些 promise,那么使用不可避免的 Deferreds 编写自己的函数应该是您的最后一个选择。请考虑切换到功能更强大的库,和/或针对当前库提交 bug。它的维护者应该能够从现有函数中派生组合,为你实现一个新的辅助函数和/或帮助识别需要处理的边缘情况。
评论
setTimeout
setTimeout
的函数”,而是“函数 setTimeout
本身”。
setTimeout
本身明显不同,不是吗?setTimeout
现在 7 年后,这个问题有了更简单的答案:
如何避免显式构造函数反模式?
使用 s,然后每个 Promise!async function
await
而不是手动构建嵌套的 Promise 链,例如:
function promised() {
return new Promise(function(resolve) {
getOtherPromise().then(function(result) {
getAnotherPromise(result).then(function(result2) {
resolve(result2);
});
});
});
}
只需转动函数并使用关键字停止函数的执行,直到 Promise 解决:async
await
async function promised() {
const result = await getOtherPromise();
const result2 = await getAnotherPromise(result);
return result2;
}
这有很多好处:
- 调用该函数始终返回一个 Promise,该 Promise 使用返回的值进行解析,并在异步函数内部抛出错误时拒绝
async
- 如果 ed Promise 拒绝,则会在异步函数中抛出错误,因此您可以像同步错误一样使用它。
await
try { ... } catch(error) { ... }
- 你可以在循环和 if 分支中,使大部分 Promise 链逻辑变得微不足道
await
- 尽管异步函数的行为大多类似于 Promise 链,但它们更易于阅读(也更易于推理)
如何等待
回电?
如果回调只回调一次,并且您调用的 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
停止执行,调用异步函数
会直接返回结果吗?
不。如果调用异步函数,则始终返回 Promise。然后,您也可以在异步函数中执行 Promise。您不能在同步函数中等待结果(您必须调用并附加回调)。await
.then
从概念上讲,同步 s 总是在一个作业中运行到完成,而 s 同步运行,直到它们到达 ,然后它们在另一个作业中继续运行。function
async function
await
评论
no-floating-promises
no-misused-promises
await
上一个:有CSS父选择器吗?
下一个:使用动态计算的名称访问对象属性
评论
catch
getStuffDone
Promise
.then
.catch
.then(resolve).catch(reject)