bygrace
bygrace

Reputation: 5988

rxjs repeat api call based on duration specified in response

Context

I need to get data from the server that has a variable expiration (specified in the response). Once it expires I need to get it again. So I would like to create a stream that makes an asynchronous request and repeats that request after a time that is specified in the response of that request.

What I tried

Here is my first attempt but the repeatWhen doesn't have access to the last response. Instead of doing the repeat every 1000ms I would like to do it based on the expiration property on the response.

const { Observable, defer, of } = rxjs;
const { repeatWhen, delay, map, take } = rxjs.operators;

let count = 0;
function api() {
  return of({ data: count++, expiration: Math.random() * 1000 });
}

defer(() => api()).pipe(
  repeatWhen((notification) => notification.pipe(delay(1000))),
  map((response) => response.data),
  take(5)
).subscribe((x) => { console.log(x); });
<script src="https://unpkg.com/rxjs@rc/bundles/rxjs.umd.min.js"></script>

Question

Using rxjs, how can I make an api call and repeat it on a delay based on its last response?

UPDATE

This technically does what I want but it is a bit hacky... so I would like a better solution.

const { Observable, defer, of, BehaviorSubject, timer } = rxjs;
const { repeatWhen, delay, map, take, tap, switchMap } = rxjs.operators;

let count = 0;
function api() {
  return of({ data: count++, expiration: Math.random() * 1000 });
}

const trigger = new BehaviorSubject(0);
trigger.pipe(
  switchMap((expiration) => timer(expiration)),
  switchMap(() => api().pipe(
    tap((response) => { trigger.next(response.expiration); })
  )),
  take(5)
).subscribe((x) => { console.log(x); });
<script src="https://unpkg.com/rxjs@rc/bundles/rxjs.umd.min.js"></script>

Upvotes: 5

Views: 2163

Answers (2)

CozyAzure
CozyAzure

Reputation: 8478

This can be easily achieved using the .expand() operator, which is meant for recursive purposes. The exact solution is only a few lines:

api()
    .expand(({expiration}) => api().delay(expiration))
    .take(5)
    .subscribe(x=>console.log(x));

Here is the JSBin.

Upvotes: 4

Picci
Picci

Reputation: 17762

I am not sure I understood completely your question, but what about something like this

function api() {
  return of(Math.random() * 10000);
}

defer(() => api()).pipe(
    tap(delay => console.log('delay', delay)),
    switchMap(data => interval(data)),
    take(5)
).subscribe(console.log);

UPDATED ANSWER AFTER COMMENT

You are already doing the repetition based on what the api is returning to you, and not every 1000 ms. If you run this code it should be clear

let count = 0;
const expiration = Math.random() * 1000;

function api() {
    return of({ count, expiration});
}

defer(() => api()).pipe(
    tap(delay => console.log('delay', delay.expiration)),
    switchMap(data => interval(data.expiration).pipe(map(() => data))),
    map(data => ({expiration: data.expiration, count: count++})),
    take(5)
).subscribe(console.log);

SECOND UPDATE AFTER SECOND COMMENT

If I understand now what you want to achieve, this should help you

defer(() => api()).pipe(
    tap(data => console.log('I do something with this data', data)),
    switchMap(data => interval(data.expiration)),
    switchMap(() => api()),
    take(5)
).subscribe(data => console.log('I do something with this data', data));

Upvotes: 0

Related Questions