Rxjs: Retry Observable.fromPromise desired behavior?

Created on 8 Apr 2016  路  14Comments  路  Source: ReactiveX/rxjs

RxJS version:
beta 4
Code to reproduce:

var count = 0

    function mockAjax (query) {
      if (++count % 2 === 0) {
        return Promise.resolve(query)
      }
      else {
        return Promise.reject('promise error on: ' + query)
      }
    }

    const ajaxResp$ = Observable.fromPromise(['cats'])
      .flatMap(topic =>
           Observable.from(mockAjax(topic))
           .retry(3)
      )
      .subscribe()

Expected behavior:
Observable.from(mockAjax(topic)) to be retried.

Actual behavior:
It does not retry.
Additional information:

Most helpful comment

@rickmed No worries, tbh I see this misconception _a lot_.

In a nutshell all fromPromise is doing is mapping calls from promise.then onto observable.next() + observable.complete() or observable.error()

A highly simplified example being:

Rx.Observable.fromPromise = function(promise) {
  return Observable.create(observer => {
    promise
      .then(o => {observer.next(o); observer.complete(); })
      .catch(e => observer.error(e));

  });
};

At first glance that would appear to be what you had written, however where your example is re-invoking the method and creating a new Promise, the actual implementation resubscribing the the Observable that wraps the Promise is simply adding a new then block to the same promise without recreating it.

So by "conform to Observable interface" I just mean that it is simply wrapping the Promise interface so that it can play nice with the rest of the RxJS world.

And yes I would recommend rxjs-dom over promises for exactly that reason, in addition to not having to worry about any conversions (though many of the methods can also implicitly accept Promise's so it isn't as big a concern).

All 14 comments

Yes this is expected. You can't retry a promise once it is in flight and your method is only called once when the Promise is first created.

@paulpdaniels but retry there is on the observable, not on the promise. Otherwise, I would just go mockAjax directly instead of wrapping it in an observable.
EDIT: PS: this works

.flatMap(topic =>
        Observable$.create(observer => {
          mockAjax(topic)
           .then(o => observer.next(o))
           .catch(e => observer.error(e))
        })
        .retry(3)
)

Shouldn't the above work similarly?

Wrapping a Promise in an Observable doesn't change it's behavior, it just makes it conform to the Observable interface. In the second example you are creating a new promise when you retry, because each subscription calls the subscription function again. If you want retry semantics you should wrap the mockAjax call in a defer block. That will reinvoke the mockAjax call everytime there is a new subscription.

      .flatMap(topic =>
           Observable.defer(() => mockAjax(topic))
           .retry(3)
      )
      .subscribe()


@paulpdaniels got it -I just thought the implementation of fromPromise was similar to what I wrote above but I guess it is something different.

If you have time to elaborate this ", it just makes it conform to the Observable interface" would be great...

Anyways, I guess for this cases it is best to use rxjs dom, no? + you get cancellation (vs just ignoring the promise's resolve value)

Thanks for the quick responses!

@rickmed No worries, tbh I see this misconception _a lot_.

In a nutshell all fromPromise is doing is mapping calls from promise.then onto observable.next() + observable.complete() or observable.error()

A highly simplified example being:

Rx.Observable.fromPromise = function(promise) {
  return Observable.create(observer => {
    promise
      .then(o => {observer.next(o); observer.complete(); })
      .catch(e => observer.error(e));

  });
};

At first glance that would appear to be what you had written, however where your example is re-invoking the method and creating a new Promise, the actual implementation resubscribing the the Observable that wraps the Promise is simply adding a new then block to the same promise without recreating it.

So by "conform to Observable interface" I just mean that it is simply wrapping the Promise interface so that it can play nice with the rest of the RxJS world.

And yes I would recommend rxjs-dom over promises for exactly that reason, in addition to not having to worry about any conversions (though many of the methods can also implicitly accept Promise's so it isn't as big a concern).

@paulpdaniels I think I get it.

If I resubscribe to a fromPromise observable I would just get as input the resolved/rejected value from the promise. So the benefit is just an opportunity to further manipulate that value? (using the additional .then blocks?

Again, thank you for your time.

@rickmed A promise is an already started operation, observables describe _functions_ for sequences of operations. An observable for one thing is more similar to a function that returns a promise than a promise itself.

Retry can be made to work (as shown above) with functions that return a promise - not with promises themselves.

@paulpdaniels:

No worries, tbh I see this misconception a lot.

I faced the same issue today. Just wondering, if there are that many people struggling with this, shouldn't it be documented somewhere? It could be as simple as the following snippet, with a short description on why it differs from Rx.Observable.fromPromise:

Rx.Observable.defer(() => functionReturningPromise())
.retry(n)

@paulpdaniels is correct here. Since promises are not lazy and cannot be retried, wrapping them in an observable doesn't make them able to be retried.

@sanderploegsma that's a good suggestion. Probably wouldn't hurt to put that on the from and fromPromise docs

Just adding my 2 cents here... We run in the same issue and luckily landed on this post. Would it really be a problem to change fromPromise API or create another flavor of it that would "generate" a "retryable" observable ?
Even though most people using rx are knowledgeable enough in the area of async and Promises, it only sounds natural that a large majority would take an Observer created with .fromPromise() as a regular observable where you can apply the .retry() method on it and that they assume that the "fire once" limitation of Promises is being lifted by .fromPromise().

You can't "retry" a promise, this is just the spec. You can retry a callback that happens to return a promise. Which can be done really easily via Obsevable.create(o => Observable.fromPromise(myPromiseFactory()));. This is fully retryable, because each subscription will invoke myPromiseFactory().

similarly, this also works:

return Observable.of([]).switchMap(() => {
  return fromPromise( somePromise )
});

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings