如何等待可选的观测

时间:2018-11-30 15:54:35

标签: angular typescript rxjs

在我的Angular应用程序中,我正在进行可选的HTTP调用以检查用户是否存在。根据此呼叫的结果,我想打另一个电话或停止处理。在不诉诸.toPromise()的情况下,如何等待在if块中进行的呼叫?

  onSomeEvent() {
    const contact: IContact = {
      ...this.userForm.value
    };

    let cancelProcessing = false;

    if (this.isNewUser()) {
      // *** This is optional
      this.userService.duplicateUserSearch(contact.email, contact.userName)
        .subscribe(result => {
          if (result.result !== '') {
            cancelProcessing = true;
          }
        });
    }

    // *** This statement is hit before the observable completes
    if (cancelProcessing) {
      return;
    }

    //  *** which means that this runs when it shouldn't
    this.userService.updateContact(contact)
      .subscribe(res => {
        this.activeModal.close(res);
      });
  }

3 个答案:

答案 0 :(得分:2)

正如您在问题中所展示的那样,带有Observables的If / Then逻辑可能很棘手。关于此here的讨论很好。

您已经有了一些可以在某些条件下使用的答案,我本该冲浪,然后将您留在范或杰弗里的精干手中,但是您对一个答案的评论使我很感兴趣:“如果添加更多条件会怎样?”。那引起了我的兴趣,因此,我想找到一种基于可观察/功能,清晰易读且易于扩展的模式,如果以后再决定添加其他条件的话。一个好的问题使我很难思考,而您当然已经做到了。 :)

我尝试应用上面链接到您的问题的文章中建议的模式,下面的代码正是我想到的。正如我在上面对您的原始问题的评论中提到的那样,当用户不是新用户时,我不确定100%确定您打算更新该联系人,但是现在我假设您愿意。免责声明:我没有严格测试过诸如创建Stackblitz和创建各种测试用例之类的内容,尽管我通常希望以此方式回答这样的问题,因此对于以下代码中的任何错误或意外后果,我深表歉意。 las,周末来了。 ;)

在本文的模式中,建议通过if / then逻辑为所有可能的路径创建分支。我提出了两个主要分支,这将导致调用updateContact()。请注意,使用这种模式很容易在任何分支中添加tap()以在需要时进行其他工作。如果我错过了您希望添加的分支,那么只需添加即可也很容易。

此模式的核心是结尾处的merge()。这将创建一个单一的可观察对象,它将合并传入的两个对象。如果其中任何一个完成,则订阅将执行并运行updateContact()。在这种情况下,由于isNewUser()的过滤器无法保证它们都处于活动状态,所以它们将永远不会全部完成,但是如果您在其他地方应用此模式,则可能只想关心第一个就可以添加take(1)异步的“获胜”。

由于我认为这是最佳做法,因此我也显示了订阅和退订。

onSomeEventSub : Subscription; // component scope variable to add to later unsubscribe with

onSomeEvent() {
    const contact: IContact = {
      ...this.userForm.value
    };

    // define duplicateUserSearch$ now for easier readability below
    const duplicateUserSearch$: Observable<boolean> = 
        this.userService.duplicateUserSearch(contact.email, contact.userName).pipe(
            map(result => (result.result === '')));

    // First, create a shareable source for eventual merge of all branches.
    // of(0) is completely arbitrary.  It would be more interesting to tie
    // this to the original event, but that wasn't part of the question.  :)
    const source$ = of(0).pipe(
        share() // same observable will have multiple observers
    );

    // branch 1 is for when the user is a new user and duplicate logic asserts
    const isNewUserAndIsDupe$ = source$.pipe(
        filter(() => isNewUser()),
        mergeMap(() => duplicateUserSearch$),
        filter(res => res)
    );

    // branch 2 is when the user is NOT a new user
    const isNotNewUser$ = source$.pipe(
        filter(() => !isNewUser())
    );

    // and finally the merge that pulls this all together and subscribes
    this.onSomeEventSub = merge(isNewUserAndIsDupe$, isNotNewUser$).pipe(
        mergeMap(() => this.userService.updateContact(contact))
    ).subscribe(res => this.activeModal.close(res));

}

ngOnDestroy() {
    if (this.onSomeEventSub) this.onSomeEventSub.unsubscribe();
}

答案 1 :(得分:1)

一种更加实用的方式。

onSomeEvent() {
  const contact: IContact = {
    ...this.userForm.value
  };
const updateContact=this.userService.updateContact(contact)
  .pipe(tap(res => {
    this.activeModal.close(res);
  }))

return of(this.isNewUser()).pipe(mergeMap(newUser=>{
   if(!newUser)
      return updateContact
   return this.userService.duplicateUserSearch(contact.email, contact.userName)
     .pipe(mergeMap(result=>result.result === ''?updateConcat:empty()))  
}))

}

// execute
this.onSomeEvent().subscribe()

答案 2 :(得分:1)

问题在于onSomeEvent是同步执行的,而cancelProcessing的分配是异步执行的-稍后。我认为您可以改为执行以下操作(RxJS 5):

onSomeEvent() {
    const contact: IContact = {
        ...this.userForm.value
    };

    if (this.isNewUser()) {
        this.userService.duplicateUserSearch(contact.email, contact.userName)
            .filter((result) => result.result === '')
            .switchMap(() => this.userService.updateContact(contact))
            .subscribe((res) => this.activeModal.close(res));
    } else {
        this.userService.updateContact(contact)
            .subscribe((res) => this.activeModal.close(res));
    }
}

If子句

首先,第一个条件filter对可观察流进行编辑。这将返回一个可观察对象,该对象将仅在duplicateUserSearch 为空字符串时转发来自result.result可观察对象的项目。

接下来,传递的值将像您在示例中一样被丢弃,并由updateContact返回的可观察值代替,只有在某些值通过了以前使用的过滤器后才按需调用。

switchMap 展平输入值,这意味着如果它们是可观察的,它们将被订阅,并且它们的值将通过switchMap返回的observable而不是observable传播。实例本身。

最后,我们可以订阅switchMap返回的流,并直接观察updateContact返回的值。

其他条款

如果不是新用户,则无条件调用updateContact,而无需先与duplicateUserSearch进行检查。


使用RxJS 6.x时,pipe运算符用于一次性指定运算符的顺序:

onSomeEvent() {
    const contact: IContact = {
        ...this.userForm.value
    };

    if (this.isNewUser()) {
        this.userService.duplicateUserSearch(
            contact.email,
            contact.userName,
        ).pipe(
            filter((result) => result.result === ''),
            switchMap(() => this.userService.updateContact(contact)),
        ).subscribe((res) => this.activeModal.close(res));
    } else {
        this.userService.updateContact(contact)
            .subscribe((res) => this.activeModal.close(res));
    }
}