takeWhile完成后链接的Observable没有被调用?

时间:2017-07-25 20:20:02

标签: rxjs observable

我有以下方法,应该像这样调用:

    应该调用
  1. registerDomain并返回operationId
  2. 10秒之后,getOperationDetail应该调用operationId
  3. getOperationDetail应该每隔10秒调用一次,直到successful返回。
  4. getOperationDetail完成后,应调用createRecordSets
  5. 最后,应该调用getChangeStatus,直到它返回INSYNC
  6. 如果任何api调用抛出异常,我该如何处理客户端的错误?
  7. 下面的代码调用registerDomain和getOperationDetail,但是在getOperationDetail完成后,它不会移动到createRecordSets。

      registerDomain(domain) {
        return this._adminService.registerDomain(domain)
          .concatMap(operation => this.getOperationDetail(operation.OperationId))
          .concatMap(() => this._adminService.createRecordSets(domain));
      }
    
      getOperationDetail(operationId) {
        return Observable.interval(10000)
          .mergeMap(() => this._adminService.getOperationDetail(operationId))
          .takeWhile((info) => info.Status.Value !== 'SUCCESSFUL');
      }
      createRecordSets(caseWebsiteUrl) {
        return this._adminService.createRecordSets(caseWebsiteUrl.Url)
            .concatMap(registerId => this.getChangeStatus(registerId));
      }
    
      getChangeStatus(registerId) {
        return Observable.interval(5000)
          .mergeMap(() => this._adminService.getChange(registerId))
          .takeWhile((info) => info.ChangeInfo.Status.Value !== 'INSYNC');
      }
    

    我更新了getOperationDetail以使用first运算符:

      getOperationDetail(operationId) {
        return Observable.interval(3000)
          .mergeMap(() => this._adminService.getOperationDetail(operationId))
          .first((info) =>  info.Status.Value === 'SUCCESSFUL')
    
      }
    

    现在它实际上会调用createRecordSets,但在createRecordSets之后,它会继续调用getOperationDetail大约13次并最终调用getChangeStatus。我正在看它的方式,我认为它会:

    1. 致电getOperationDetail,直至收到SUCCESS
    2. 一次致电createRecordSets
    3. 致电getChangeStatus,直至收到INSYNC
    4. 完成。
    5. 为什么要追加电话?

      我将registerDomain更改为:

       registerDomain(domain) {
          return this._adminService.registerDomain(domain)
            .concatMap(operation => this.getOperationDetail(operation.OperationId))
              .concatMap((op) => this.createRecordSets(op));
      

      .concatMap((op) => this.createRecordSets(op))之后我this.getOperationDetail链接之前。一旦我把它移到外面,它开始按预期工作。我不确定为什么。有人可以解释一下吗?

1 个答案:

答案 0 :(得分:3)

takeWhile满足满足指定条件的值时,它将完成observable而不传播该值。这意味着下一个链式运算符将不会接收该值,也不会调用其回调。

假设在您的示例中,对this._adminService.getOperationDetail(...)的前两次调用导致非成功状态,第三次调用成功。这意味着getOperationDetail()返回的可观察量只会产生两个info值,每个值都具有非成功状态。而且可能也很重要的是,下一个链式concatMap运算符将针对每个非成功值调用其回调,这意味着createRecordSets()将被调用两次。我想你可能想避免这种情况。

我建议改为使用first运算符:

getOperationDetail(operationId) {
    return Observable.interval(10000)
        .concatMap(() => this._adminService.getOperationDetail(operationId))
        .first(info => info.Status.Value !== 'SUCCESSFUL');
}

这样getOperationDetail()只会产生一个成功的" this._adminService.getOperationDetail(operationId)成功后的价值。 first运算符发出与指定条件匹配的源observable的第一个值,然后完成。

在处理错误时,catchretry运算符可能很有用。

<强>更新

您遇到的意外行为(getOperationDetail()完成后first()继续被调用)似乎是rxjs中的bug。如this issue中所述,

  

每个take-ish运算符(一个比其源Observable更早完成的运算符),在与延长订阅的运算符(此处为switchMap)结合使用时,将继续订阅源Observable。

firsttakeWhile都是这种“运营商”和“运营商”的例子。&#34;延长&#34;例如,订阅是switchMapconcatMapmergeMap。在下面的示例中,数字将保持记录状态,而concatMap的内部可观察值正在发出值:

var takeish$ = Rx.Observable.interval(200)
    // will keep logging until inner observable of `concatMap` is completed
    .do(x => console.log(x))
    .takeWhile(x => x < 2);

var source = takeish$
    .concatMap(x => Rx.Observable.interval(200).take(10))
    .subscribe();

看起来这可以通过将包含这样一个take-ish运算符的observable转换为更高阶的可观察量来解决 - 就像你已经完成的那样:

var takeish$ = Rx.Observable.interval(200)
    // will log only 0, 1, 2
    .do(x => console.log(x))
    .takeWhile(x => x < 2);

var source = Rx.Observable.of(null)
    .switchMap(() => takeish$)
    .concatMap(x => Rx.Observable.interval(200).take(1))
    .subscribe();

更新2:

从rxjs版本5.4.2开始,上述错误似乎仍然存在。例如,当first满足指定条件时,它会影响first运算符的源可观察量是否取消订阅。当first运算符紧跟concatMap后,其源可观察量将不会被取消订阅,并将继续发出值,直到concatMap的内部可观察量完成。在您的情况下,这意味着this._adminService.getOperationDetail()会一直被调用,直到createRecordSets()返回的observable完成为止。

这里简化了您的示例来说明行为:

&#13;
&#13;
function registerDomain() {
    return Rx.Observable.of("operation")
        .concatMap(() => getOperationDetail()
            .concatMap(() => Rx.Observable.interval(200).take(5)));
}

function getOperationDetail() {
    return Rx.Observable.interval(100)
        // console.log() instead of the actual service call
        .do(x => console.log(x))
        .first(x => x === 2);
}

registerDomain().subscribe();
&#13;
<script src="https://unpkg.com/@reactivex/rxjs@5.0.3/dist/global/Rx.js"></script>
&#13;
&#13;
&#13;

如果我们展开第一个concatMap运算符的内部可观察量,我们将得到以下可观察值:

Rx.Observable.interval(100)
    .do(x => console.log(x))
    .first(x => x === 2)
    .concatMap(() => Rx.Observable.interval(200).take(5));

请注意,first后面紧跟concatMap,这会阻止first运算符(即interval(100).do(x => console.log(x))的源可观察性被取消订阅。值将被记录(或者在您的情况下,服务调用将继续发送),直到concatMap的内部可观察量(即interval(200).take(5))完成。

如果我们修改上面的示例并将第二个concatMap移出第一个concatMap的内部可观察对象,则first将不再与其链接,并将取消订阅一旦条件满足,源可观察,意味着间隔将停止发射值,不再记录更多数字(或不再发送服务请求):

&#13;
&#13;
function registerDomain() {
    return Rx.Observable.of("operation")
        .concatMap(() => getOperationDetail())
        .concatMap(() => Rx.Observable.interval(200).take(5));
}

function getOperationDetail() {
    return Rx.Observable.interval(100)
        // console.log() instead of the actual service call
        .do(x => console.log(x))
        .first(x => x === 2);
}

registerDomain().subscribe();
&#13;
<script src="https://unpkg.com/@reactivex/rxjs@5.0.3/dist/global/Rx.js"></script>
&#13;
&#13;
&#13;

在这种情况下,内在的可观察性可以简单地扩展到:

Rx.Observable.interval(100)
    .do(x => console.log(x))
    .first(x => x === 2)

请注意first后面不再有concatMap

值得一提的是,在两种情况下registerDomain()返回的可观察值都会产生完全相同的值,如果我们将记录从do()运算符移动到subscribe(),则会写入相同的值在两种情况下都到控制台:

registerDomain.subscribe(x => console.log(x));