通过多个 classes/methods 突破承诺链 运行
Break out of promise chain running through multiple classes/methods
语言:打字稿 3.5x
环境:Angular8.x,RxJS6.x
我想弄清楚如何摆脱看起来(简化)如下所示的承诺链:
ClassA.methodA1 is called
-methodA1 does an if/then check and if true, calls ClassB.methodB1
--methodB1 calls a different function and combines the results with a parameter passed to it, and ultimately calls ClassC.methodC1
---methodC1 does some validation and then calls ClassD.methodD1 if valid
----methodD1 makes an async call (to a DB) and waits for it to finish before returning back the value it received (an ID).
When methodA1 receives the id, it calls ClassA.methodA2 and passes that Id in as a parameter
methodD1 调用的方法不在我的控制范围内,return是一个 Promise。我无法改变这一点。但是,我需要 methodA1 等到 methodD1 完成,并且它收到 ID - 每个干预方法只是将 return 值传递回其调用者。
即使是这个简化版也有味道。 NONE 的中间方法(methodB1 或 methodC1,实际代码中实际上还有 2 个)需要异步。他们目前的唯一原因是我需要一切都等到 methodD1 完成后再继续。每个干预方法只是 return 从它调用的方法 return 中编辑 Promise。
我用 async/await 进行了重构,虽然代码更少了,但它仍然是一系列方法,它们只是将它们从方法调用中接收到的 return 值传递到链中,并且它是在干预方法上仍然有一堆不必要的async/await。
我重构为 RxJS Subjects 并让 methodA1 订阅,然后 methodD1 在它完成时调用 .next(id)
但这并没有真正感觉更好。我的应用程序中可能有几十个类似的流程,感觉走 Subject 路线会带来很多额外的管道开销,并且要弄清楚哪个订阅响应属于哪个订阅实例会出现问题。
我的直觉告诉我 Subject 路线是正确的方法,但我想知道我是否遗漏了一些可以使它更清晰的东西。是否有一些内置机制来标记对给定订阅实例的订阅响应,以便订阅代码仅处理对 ITS 调用的响应,然后取消订阅?
我能想到的唯一其他方法,很简单 UGLY 是将我的 ClassA 实例一直传递到 ClassD 并让 ClassD 在收到数据库中的 ID。但这更糟。
有什么我想念的吗?如果是这样,什么?感谢任何指导或模式参考。
The only other approach I can think of is to pass my ClassA instance all the way down to ClassD and have ClassD call methodA2 when it receives the id from the DB. But this is even worse.
这本质上是一个回调(实例接收方法调用,而不是调用普通函数),通常被认为比简单地 returning 承诺更糟糕。
请注意,您的原始方法没有任何问题,如果只有组件 D 调用数据库(异步)并且所有其他模块都依赖于 D,因为它们执行使用数据库的操作,那么其他模块自然也变成异步的。异步 (promise-returning) 调用处于尾部位置似乎是巧合,如果组件要按顺序执行多个数据库调用,则可能会发生变化。
但是, 您错过了另一种方法。当您的所有函数只执行一些 combination/translation/validation 参数,并最终将它们传递给不同的方法时,它是适用的。与其让 B 调用 C 本身,不如简单地 return 将经过验证的参数传递给 A,然后让 A 直接调用 C。这样就可以让B和C完全同步和单元测试,只有A调用D的异步方法。
感谢您的投入。虽然承诺链是一种合法的方法,但它让人感觉不稳定,更难测试和推理。 YMMV.
我最终对 RxJS Subjects 进行了稍微修改的路线。与我之前的不同之处在于,Subjects 不是永久的和硬编码的,而是根据需要动态创建并存储在 Map 中。一旦不再需要它们,它们就会被删除。通过给每个人一个不同的名称,我不需要验证哪个响应 "belongs" 对哪个订阅 - 它都是通过不同的名称作为地图的键来锁定的。
我唯一不喜欢这种方法的地方是必须在链中一直传递不同的名称,但我可以接受。一切都朝着一个方向流动 - "down" 链条 - 这对我来说更容易推理。
这是我最终得到的伪代码版本(为简洁起见删除了所有错误检查和 class 实例化):
export class ClassA {
methodA1(param1){
if(someCondition){
const theKey = this.generateUniqueKey();
DispatcherInstance.subjects.set(theKey, new Subject<number>());
const sub = DispatcherInstance.get(theKey).subscribe( (dbId: number) => {
sub.unsubscribe();
DispatcherInstance.subjects.delete(theKey);
this.doNextStepWithId(dbId);
}
classBInstance.methodB1(param1, theKey);
}
}
}
export class ClassB {
methodB1(param1, theKey): void {
const result = this.methodB2();
classCInstance.methodC1(param1, result, theKey);
}
}
export class ClassC {
methodC1(param1, result, theKey): void {
if(this.validate(param1, result)){
ClassDInstance.methodD1(param1, theKey);
}
}
}
export class ClassD {
methodD1(param1, theKey: string): void {
db.someFunc(param1).then( (dbId: number) => {
DispatcherInstance.subjects.get(theKey).next(dbId);
});
}
}
export class Dispatcher {
const subjects: Map<string, Subject<number>> = new Map<string, Subject<number>>()
}
语言:打字稿 3.5x
环境:Angular8.x,RxJS6.x
我想弄清楚如何摆脱看起来(简化)如下所示的承诺链:
ClassA.methodA1 is called
-methodA1 does an if/then check and if true, calls ClassB.methodB1
--methodB1 calls a different function and combines the results with a parameter passed to it, and ultimately calls ClassC.methodC1
---methodC1 does some validation and then calls ClassD.methodD1 if valid
----methodD1 makes an async call (to a DB) and waits for it to finish before returning back the value it received (an ID).
When methodA1 receives the id, it calls ClassA.methodA2 and passes that Id in as a parameter
methodD1 调用的方法不在我的控制范围内,return是一个 Promise。我无法改变这一点。但是,我需要 methodA1 等到 methodD1 完成,并且它收到 ID - 每个干预方法只是将 return 值传递回其调用者。
即使是这个简化版也有味道。 NONE 的中间方法(methodB1 或 methodC1,实际代码中实际上还有 2 个)需要异步。他们目前的唯一原因是我需要一切都等到 methodD1 完成后再继续。每个干预方法只是 return 从它调用的方法 return 中编辑 Promise。
我用 async/await 进行了重构,虽然代码更少了,但它仍然是一系列方法,它们只是将它们从方法调用中接收到的 return 值传递到链中,并且它是在干预方法上仍然有一堆不必要的async/await。
我重构为 RxJS Subjects 并让 methodA1 订阅,然后 methodD1 在它完成时调用 .next(id)
但这并没有真正感觉更好。我的应用程序中可能有几十个类似的流程,感觉走 Subject 路线会带来很多额外的管道开销,并且要弄清楚哪个订阅响应属于哪个订阅实例会出现问题。
我的直觉告诉我 Subject 路线是正确的方法,但我想知道我是否遗漏了一些可以使它更清晰的东西。是否有一些内置机制来标记对给定订阅实例的订阅响应,以便订阅代码仅处理对 ITS 调用的响应,然后取消订阅?
我能想到的唯一其他方法,很简单 UGLY 是将我的 ClassA 实例一直传递到 ClassD 并让 ClassD 在收到数据库中的 ID。但这更糟。
有什么我想念的吗?如果是这样,什么?感谢任何指导或模式参考。
The only other approach I can think of is to pass my ClassA instance all the way down to ClassD and have ClassD call methodA2 when it receives the id from the DB. But this is even worse.
这本质上是一个回调(实例接收方法调用,而不是调用普通函数),通常被认为比简单地 returning 承诺更糟糕。
请注意,您的原始方法没有任何问题,如果只有组件 D 调用数据库(异步)并且所有其他模块都依赖于 D,因为它们执行使用数据库的操作,那么其他模块自然也变成异步的。异步 (promise-returning) 调用处于尾部位置似乎是巧合,如果组件要按顺序执行多个数据库调用,则可能会发生变化。
但是, 您错过了另一种方法。当您的所有函数只执行一些 combination/translation/validation 参数,并最终将它们传递给不同的方法时,它是适用的。与其让 B 调用 C 本身,不如简单地 return 将经过验证的参数传递给 A,然后让 A 直接调用 C。这样就可以让B和C完全同步和单元测试,只有A调用D的异步方法。
感谢您的投入。虽然承诺链是一种合法的方法,但它让人感觉不稳定,更难测试和推理。 YMMV.
我最终对 RxJS Subjects 进行了稍微修改的路线。与我之前的不同之处在于,Subjects 不是永久的和硬编码的,而是根据需要动态创建并存储在 Map 中。一旦不再需要它们,它们就会被删除。通过给每个人一个不同的名称,我不需要验证哪个响应 "belongs" 对哪个订阅 - 它都是通过不同的名称作为地图的键来锁定的。
我唯一不喜欢这种方法的地方是必须在链中一直传递不同的名称,但我可以接受。一切都朝着一个方向流动 - "down" 链条 - 这对我来说更容易推理。
这是我最终得到的伪代码版本(为简洁起见删除了所有错误检查和 class 实例化):
export class ClassA {
methodA1(param1){
if(someCondition){
const theKey = this.generateUniqueKey();
DispatcherInstance.subjects.set(theKey, new Subject<number>());
const sub = DispatcherInstance.get(theKey).subscribe( (dbId: number) => {
sub.unsubscribe();
DispatcherInstance.subjects.delete(theKey);
this.doNextStepWithId(dbId);
}
classBInstance.methodB1(param1, theKey);
}
}
}
export class ClassB {
methodB1(param1, theKey): void {
const result = this.methodB2();
classCInstance.methodC1(param1, result, theKey);
}
}
export class ClassC {
methodC1(param1, result, theKey): void {
if(this.validate(param1, result)){
ClassDInstance.methodD1(param1, theKey);
}
}
}
export class ClassD {
methodD1(param1, theKey: string): void {
db.someFunc(param1).then( (dbId: number) => {
DispatcherInstance.subjects.get(theKey).next(dbId);
});
}
}
export class Dispatcher {
const subjects: Map<string, Subject<number>> = new Map<string, Subject<number>>()
}