Javascript 打破通过多个类/方法运行的承诺链

Javascript 打破通过多个类/方法运行的承诺链,javascript,typescript,promise,rxjs6,Javascript,Typescript,Promise,Rxjs6,语言:Typescript 3.5x 环境:Angular 8.x,RxJS 6.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 pas

语言:Typescript 3.5x

环境:Angular 8.x,RxJS 6.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
Method1调用的方法不在我的控制范围内,并返回一个承诺。我不能改变。然而,我需要methodA1等待methodD1完成,它接收到ID——每个介入的方法只是将返回值传递回调用方

即使是这个简化版本也有异味。中间的方法(methodB1或methodC1,实际代码中还有两个)都不需要异步。他们目前的唯一原因是,我需要等到Method1完成后才能继续。每个介入方法只返回从其调用的方法返回的承诺

我使用async/await进行了重构,虽然代码较少,但仍然是一系列方法简单地将从方法调用接收到的返回值传递回链中,而且在中间的方法上仍然是一堆不必要的async/await

我重构到RxJS主题,让Method1订阅,然后Method1调用
。下一步(id)
,当它完成时,但这感觉并没有任何改善。我的应用程序中可能会有几十个类似的流,这让我感觉走主题路线会有很多额外的管道开销,并且保持哪个订阅响应属于哪个订阅实例会有问题

我的直觉告诉我,主题路线是正确的方法,但我想知道是否有什么我遗漏了,使它更干净。是否有一些内置机制来标记对给定订阅实例的订阅响应,以便订阅代码只处理对其调用的响应,然后取消订阅

我能想到的唯一的另一种方法,就是将我的ClassA实例一直传递到ClassD,并在它从DB接收到id时使用ClassD调用methodA2,这显然是丑陋的。但这更糟糕

有什么我遗漏的吗?如果是,什么?欢迎提供任何指导或模式参考

我唯一能想到的另一种方法是将我的ClassA实例一直传递到ClassD,并在它从DB接收id时使用ClassD调用methodA2。但这更糟糕

实际上,回调(实例接收方法调用,而不是普通函数被调用)通常被认为比简单地返回承诺更糟糕

请注意,您最初的方法没有任何错误,如果只有组件D调用数据库(这是异步的),并且所有其他模块都依赖于组件D,因为它们使用数据库,那么其他模块自然也会变得异步。异步(承诺返回)调用处于尾部位置似乎是巧合,如果组件要按顺序执行多个db调用,则可能会发生变化


然而,还有另一种方法你错过了。当您的所有函数都只是对参数进行一些组合/转换/验证,并最终将它们传递到不同的方法中时,它是适用的。它可以简单地将经过验证的参数返回给A,然后让A直接调用C,而不是让B调用C本身。这将使B和C完全同步并可进行单元测试,只有A调用D的异步方法。

感谢您的输入。虽然承诺链是一种合法的方法,但它让人感觉不可靠,更难测试和推理。YMMV

我最终选择了RxJS主题的稍微修改路线。与之前不同的是,主题不是永久性的和硬编码的,而是根据需要动态创建并存储在地图中。一旦不再需要它们,它们就会被删除。通过给每个响应指定一个不同的名称,我不需要验证哪个响应“属于”哪个订阅—它都是以不同的名称作为映射的键进行键控的

对于这种方法,我唯一不喜欢的是必须在整个链中传递不同的名称,但我可以接受。一切都朝着一个方向流动——“向下”链条——这对我来说更容易推理

以下是我最终得到的psuedo代码版本(为了简洁起见,删除了所有错误检查和类实例化):

导出类别A{
方法1(参数1){
如果(某些条件){
const theKey=this.generateUniqueKey();
DispatcherInstance.Subject.set(键,new Subject());
const sub=DispatcherInstance.get(theKey).subscribe((dbId:number)=>{
sub.取消订阅();
DispatcherInstance.subjects.delete(键);
this.doNextStepWithId(dbId);
}
方法B1(参数1,键);
}
}
}
出口B类{
方法B1(参数1,键):无效{
const result=this.methodB2();
方法1(参数1,结果,键);
}
}
出口类别C{
方法1(参数1,结果,键):无效{
if(this.validate(参数1,结果)){
方法1(参数1,键);
}
}
}
出口类别D{
method1(param1,key:string):void{
db.someFunc(param1).then((dbId:number)=>{
DispatcherInstance.subjects.get(the key).next(dbId);
});
}
}
导出类调度器{
常量主题:Map=newmap()
}

它们当前存在的唯一原因是,我需要等待Method1完成所有操作,然后才能继续。
-任何依赖于异步响应的函数本身都是异步的。要对您的链进行如此高级别的概括,给出建议有点困难,但我
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>>()
}