什么是显式承诺构造反模式以及如何避免它?
- 2024-11-02 20:59:00
- admin 原创
- 36
问题描述:
我正在编写的代码执行以下操作:
function getStuffDone(param) {
return new Promise(function(resolve, reject) {
myPromiseFn(param+1)
.then(function(val) {
resolve(val);
}).catch(function(err) {
reject(err);
});
});
}
或者:
显示代码片段
function getStuffDone(param) {
var d = Q.defer(); /* or $q.defer */
// or = new $.Deferred() etc.
myPromiseFn(param+1)
.then(function(val) { /* or .done */
d.resolve(val);
}).catch(function(err) { /* .fail */
d.reject(err);
});
return d.promise; /* or promise() */
}
Run code snippetHide resultsExpand snippet
有人告诉我这分别被称为“延迟反模式”或“Promise
构造函数反模式”,这段代码有什么不好,为什么称之为反模式?
解决方案 1:
Esailija提出的延迟反模式(现在是显式构造反模式)是初学 Promise 的人经常犯的一个反模式,我第一次使用 Promise 时也犯过这个错误。上述代码的问题在于,它没有利用 Promise 链式传递这一事实。
Promises 可以与 链接.then
,并且您可以直接返回 Promises。您的代码getStuffDone
可以重写为:
function getStuffDone(param){
return myPromiseFn(param+1); // much nicer, right?
}
Promises 的作用是让异步代码更易读,并使其行为与同步代码相似,但又不隐藏这一事实。Promises 表示对一次性操作值的抽象,它们抽象了编程语言中语句或表达式的概念。
仅当您将 API 转换为承诺且无法自动执行时,或者当您编写以这种方式更容易表达的聚合函数时,才应该使用延迟对象。
引用 Esailija 的话:
这是最常见的反模式。如果你不真正理解 Promise,并认为它们是被美化的事件发射器或回调实用程序,就很容易陷入这种模式。让我们回顾一下:Promise 是关于让异步代码保留同步代码的大部分丢失属性,例如扁平缩进和一个异常通道。
解决方案 2:
它有什么问题?
但这个模式有效!
你真幸运。不幸的是,它可能没有,因为你可能忘记了一些极端情况。在我见过的超过一半的案例中,作者忘记了处理错误处理程序:
function bad() {
return new Promise(function(resolve) {
getOtherPromise().then(function(result) {
resolve(result.property.example);
});
})
}
如果另一个承诺被拒绝,这将不会被注意,而不是传播到新的承诺(在那里它会被处理) - 并且新的承诺永远处于待处理状态,这可能会导致泄漏。
同样的事情也发生在回调代码导致错误的情况下 - 例如,当result
没有property
并且抛出异常时。这将得不到处理,导致新的承诺无法解决。
相比之下,使用.then()
会自动处理这两种情况,并在发生错误时拒绝新的承诺:
function good() {
return getOtherPromise().then(function(result) {
return result.property.example;
})
}
延迟反模式不仅麻烦,而且容易出错。使用.then()
链式调用更安全。
但我已经处理好一切了!
真的吗?很好。但是,这将非常详细和丰富,特别是如果您使用支持其他功能(如取消或消息传递)的 Promise 库。或者也许将来会这样,或者您想将您的库换成更好的库?您不会想为此重写代码。
库的方法 ( then
) 不仅原生支持所有功能,还可能进行某些优化。使用它们可能会让您的代码运行得更快,或者至少允许通过库的未来修订进行优化。
我该如何避免?
因此,每当您发现自己手动创建Promise
或Deferred
涉及已存在的承诺时,请先检查库 API。延迟反模式通常由那些将承诺 [仅] 视为观察者模式的人应用 - 但承诺不仅仅是回调:它们应该是可组合的。每个像样的库都有许多易于使用的函数,用于以各种可以想到的方式组合承诺,处理所有您不想处理的低级内容。
如果您发现需要以新的方式组合一些承诺,而现有辅助函数不支持这种方式,那么使用不可避免的 Deferred 编写自己的函数应该是您的最后选择。考虑切换到功能更强大的库,和/或针对当前库提交错误。它的维护者应该能够从现有函数中派生出组合,为您实现新的辅助函数和/或帮助识别需要处理的边缘情况。
解决方案 3:
七年后的今天,这个问题有了更简单的答案:
如何避免显式构造函数反模式?
使用async function
s,然后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,该 Promise 使用返回值解析,如果异步函数内部抛出错误则拒绝如果
await
ed Promise 拒绝,错误就会在异步函数内部引发,因此您可以像处理try { ... } catch(error) { ... }
同步错误一样处理它。你可以
await
在循环和 if 分支中使用它,这样就可以简化大部分 Promise 链逻辑尽管异步函数的行为与 Promises 链非常相似,但它们更易于阅读(也更容易推理)
我如何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
,然后它们在另一项作业中继续运行。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件