嵌套订阅的Angular Typescript返回类型

时间:2018-11-30 02:58:05

标签: angular typescript rxjs

我有一个调用多种服务方法的函数。该函数驻留在服务对象中,我需要从app.component.ts调用该服务对象,从那里我需要等到该函数完成以执行更多代码。我的问题是如何更改此代码的返回类型,以便可以从app.component.ts预订它。我的代码如下。

public registerAndGetToken() {

   this.initializeRegistration().subscribe((match)=> {

   // if initialization is success then invoke callback function
   // initializationCallback() will return a boolean (synchronous function)

   const callbackResult = this.initializationCallback(match);

   if(callbackResult) {

       this.renewToken().subscribe((tokenResult)=> {

       //renewTokenCallback is a synchronous function
       this.renewTokenCallback();
       //what to return from here??
       }, 
      (tokenError) => {
      //what to return from here?? 
      });
   }
   else {
    // what to return from here??
   }
 },
(error) => {
 // what to return from here??
});

我尝试在this.initializeRegistration()行上添加一个“返回”,然后在return Observable.of(true);上添加,并将方法签名更改为公共registerAndGetToken(): Observable<boolean>。但它不喜欢它。说

  

“预订”类型不能分配给“可观察”类型。
  “预订”类型中缺少属性“ _isScalar”。 [2322]

1 个答案:

答案 0 :(得分:1)

您的订阅太多了。 :)

我曾经听说过一些有关Observables的好建议-“怀疑订阅中的订阅”。这是一个很好的建议,因为这可能意味着如果我这样做的话,我将错误地解决该问题。作为一般经验法则,我也倾向于不订阅服务,而是将其留给组件(或者更好的是留给组件的模板)。否则,内存泄漏的机会就太多了,试图确保我全部取消订阅。

对于您而言,我很高兴您指定rxjs 5.5,因为那是在引入.pipe运算符时开始的,我认为这将使您的代码更容易编写。我对您的代码不了解很多,因此,我提供的内容并不是剪切和粘贴的解决方案,而是作为示例,说明如何对其进行重构以从服务中删除所有订阅,并最终返回一个可观察到的,可以在您的组件中进行订阅,如您在问题中所述。

以下是您可以考虑的一些代码:

public registerAndGetToken() {
    return this.initializeRegistration().pipe( // <-- return an Observable
        catchError((error) => {
            // handle case when initializeRegistration gives back an error,
            // for example:
            return throwError(`initializeRegistration() threw error: ${error.message}`);
            // This assumes the error will be bubbled up to the component
        }),
        mergeMap((match) => {
            const callbackResult = this.initializationCallback(match);
            if(callbackResult) {
                return this.renewToken().pipe(
                    tap((tokenResult)=> {
                        // Your example never uses tokenResult for anything ...
                        // so I'll assume you actually want tokenResult to bubble
                        // up all the way your component as the result ...
                        this.renewTokenCallback(); // This makes no sense to me ...
                                                   // why have a callback here?
                    }),
                    catchError((tokenError) => {
                        // add code to handle renewToken() returning an error 
                        return tokenError;
                    })
                )
            }
            else {
                // return something that can be handled inside the component 
                // when callbackResult is false.
                // for example:
                return throwError('callbackResult is false');
            }
        })
    )
}

更新-我想我会分享一些有关为什么要进行每个步骤的想法,以防万一。

  • 总体结构是,当组件订阅此链时,initializeRegistration()将成为可观察到的源(或外部)可观察事物,一旦完成,则renewToken()将被映射到链中并提供最终结果作为返回的令牌。
  • return语句开始,因为此函数将全部用于设置单个可观察的链,该链可返回并可以从组件中进行预订。
  • 接下来,以initializeRegistration()开始。订阅组件后,该函数将被执行,并且链将等待继续进行,直到完成(或给出错误)。
  • 接下来,检查错误。这时需要完成此操作,因为我们将要使用mergeMap映射(更改)链中的可观察对象,因此在执行此操作之前,我们先检查可观察源中的错误并进行处理。
  • 接下来,mergeMap。该运算符负责“订阅”,在组件依次订阅initializeRegistration时隐式订阅renewToken(),因此我们在原始函数中不需要嵌套的订阅模式。此运算符还将新的observable映射(在这种情况下,从match返回的值映射到我们的链中。因此,我们不再沿tokenResult传递,而现在沿链中的tap传递。
  • 在将结果放回到主链之前,我们将通过另外两个运算符(通过if-else逻辑在此处在子链而不是在主链中)通过管道进行处理:
  • 第一个子链运算符是renewTokenCallback()。这是我真的不了解您的业务逻辑的地方,在这方面可能是错误的。因为tap并不会真正影响整个链,而只是给我们一个可以执行某种副作用的插入点,所以我选择将其称为catchError。取决于该函数的功能,这可能不是处理此问题的正确方法...
  • 下一个子链运算符为renewToken(),这是对来自else的错误的检查
  • 现在完成了子链的处理,即返回并再次返回主链
  • 最后是callbackResult-重要的是,我们从其中返回一个实际的可观察对象,可以将其合并映射回主链。我尚不清楚该可观察到的内容-在组件上出现一些消息,告诉它$("#addspan").click(function(){console.log($(this)[0].id);});
  • 中出了点问题

我希望这会有所帮助。