不要将对象放入promise中,将promise放入对象中。
就绪是对象的一个属性。所以让它成为对象的属性。
接受的答案中描述的等待初始化方法有一个严重的限制。像这样使用await 意味着只有一个代码块可以隐式地取决于准备好的对象。这对于保证线性执行的代码来说很好,但在多线程或事件驱动的代码中它是站不住脚的。
正确构图时,问题更容易处理。目标不是等待构造,而是等待构造对象的准备就绪。这是两个完全不同的东西。甚至像数据库连接对象这样的东西可能处于就绪状态,然后返回到非就绪状态,然后再次就绪。
如果它依赖于构造函数返回时可能未完成的活动,我们如何确定准备就绪?很明显,准备就绪是对象的属性。许多框架直接表达了就绪的概念。在 JavaScript 中我们有 Promise,在 C# 中我们有 Task。两者都具有对对象属性的直接语言支持。
将构造完成承诺公开为构造对象的属性。当你的构造的异步部分完成时,它应该解决这个承诺。
.then(...) 是在 promise 解决之前还是之后执行都没有关系。 Promise 规范指出,在已解决的 Promise 上调用 then 只会立即执行处理程序。
class Foo {
public Ready: Promise.IThenable<any>;
constructor() {
...
this.Ready = new Promise((resolve, reject) => {
$.ajax(...).then(result => {
// use result
resolve(undefined);
}).fail(reject);
});
}
}
var foo = new Foo();
foo.Ready.then(() => {
// do stuff that needs foo to be ready, eg apply bindings
});
// keep going with other stuff that doesn't need to wait for foo
// using await
// code that doesn't need foo to be ready
await foo.Ready;
// code that needs foo to be ready
为什么是resolve(undefined); 而不是resolve();?因为 ES6。根据需要进行调整以适合您的目标。
来自花生画廊
使用await
在评论中,有人建议我应该使用await 构建此解决方案,以更直接地解决所提出的问题。
您可以将await 与Ready 属性一起使用,如上例所示。我不是await 的忠实粉丝,因为它要求您按依赖项对代码进行分区。您必须将所有依赖代码放在await 之后,并将所有独立代码放在它之前。这可能会掩盖代码的意图。
我鼓励人们从回调的角度来思考。像这样在精神上构建问题与 C 等语言更兼容。Promise 可以说是源自IO completion 使用的模式。
与工厂模式相比缺乏强制执行
一位赌徒认为这种模式“是个坏主意,因为没有工厂函数,就没有什么可以强制检查就绪性的不变量。它留给了客户,你几乎可以保证它会时不时地搞砸。”
他将如何阻止人们构建不执行检查的工厂方法?你在哪里画线?答案是您了解领域特定代码和框架代码之间的区别,并应用不同的标准,并具有一些常识:您会禁止除法运算符,因为没有什么可以阻止人们通过零除数?
这是我的原创作品。我设计这种设计模式是因为我对外部工厂和其他类似的变通方法不满意。尽管搜索了一段时间,但我没有找到适合我的解决方案的现有技术,所以我声称自己是这种模式的创始人,直到有争议为止。
2020 年,我发现 Stephen Cleary 在 2013 年发布了一个非常类似问题的解决方案。回顾我自己的工作,这种方法的最初痕迹出现在我几乎同时工作的代码中。我怀疑 Cleary 首先将它们放在一起,但他没有将其正式化为设计模式,也没有将其发布到其他有问题的人容易发现的地方。此外,Cleary 仅处理仅是就绪模式的一种应用的构造(见下文)。
总结
模式是
- 在它描述的对象中放一个承诺
- 将其公开为名为@987654337@ 的属性
- 始终通过 Ready 属性引用承诺(不要在客户端代码变量中捕获它)
这建立了清晰简单的语义并保证
- promise 将被创建和管理
- promise 与其描述的对象具有相同的范围
- 就绪依赖的语义在客户端代码中非常明显和清晰
- 如果 Promise 被替换(例如,由于网络状况,连接未就绪,然后再次准备就绪)通过
thing.Ready 引用它的客户端代码将始终使用当前的 Promise
在您使用该模式并让对象管理自己的 Promise 之前,最后一个是一场噩梦。这也是避免将承诺捕获到变量中的一个很好的理由。
某些对象具有暂时将它们置于无效条件的方法,并且该模式可以在该场景中使用而无需修改。 obj.Ready.then(...) 形式的代码将始终使用 Ready 属性返回的任何 Promise 属性,因此每当某些操作即将使对象状态无效时,都可以创建新的 Promise。
结束语
就绪模式并非特定于构造。它很容易应用到构造中,但它实际上是为了确保满足状态依赖关系。在异步代码时代,您需要一个系统,而 Promise 的简单声明性语义可以直接表达应该尽快采取行动的想法,并强调可能。一旦你开始用这些术语来构建事物,关于长时间运行的方法或构造函数的争论就变得没有意义了。
延迟初始化仍然有它的位置;正如我所提到的,您可以将准备就绪与延迟加载结合起来。但是,如果您可能不会使用该对象,那么为什么要尽早创建它呢?按需创建可能会更好。
还有其他解决方案。当我编写嵌入式软件时,我会预先创建所有内容,包括资源池。这使得泄漏不可能并且内存需求在编译时是已知的。但这只是解决小型封闭问题空间的方法。