Melfarion
Melfarion

Reputation: 51

Creating recursive Observable loop?

I have a hard time figuring out how to create a recursive loop to api calls with Observables.

Scenario: I call external API, which returns something like this:

{
 data: {something, something, something},
 next: "url for next set of data"
}

I need to keep on calling the same function to gather all the data into a single object as long as the response has value in the next.

I managed to do this on another project with Promises where I map the data returned into single array by using the concat() function but I somehow cannot get my head around understanding how I should do this with Observables.

Working example with using promises:

getData: function(url, params, headers){
    return new Promise((resolve, reject) => {
        axios.get(url, {
            params: params,
            headers: headers,
        }).then((response) => {
            let responseData = response.data.data[0];
            if (response.data.next) {
                this.getData(response.data.next, {}).then((resp) => {
                    for (let dataSet of responseData.dataSets) {
                        let row = resp.dataSets.find(i => i.variable === dataSet.variable)
                        if (row) {
                            dataSet.data = dataSet.data.concat(row.data)
                        }
                    }
                    resolve(responseData);
                }).catch((error) => {
                    reject(error)
                })

            } else {
                resolve(responseData);
            }
        }).catch(error => {
            reject(error)
        })
    })
}

Upvotes: 4

Views: 3886

Answers (3)

dongnhan
dongnhan

Reputation: 1768

Just got into this similar problem today, here's my attempt. I think the hard part is to think correctly about what you're trying to achieve and then find the correct operator to support that.

In this case, from the first observable, we want to expand that and continue to emit values recursively until complete. What we want to collect at the end is all the emitted values from this observable and that's when I googled the correct keywords and found toArray to support this case.

Reference: How to collect array of emitted values from Observable.from?

this.getData(endpoint, options).pipe(
  expand(({ next }) => {
    return next ? this.getData(next, options) : Observable.empty()
  }),
  toArray(), // wait for the observable to complete and collect all emitted values
)

Upvotes: 1

Melfarion
Melfarion

Reputation: 51

End solution that worked for me:

let obs = this.getData(endpoint, options).pipe(
  expand(({ next }) => {
    // This could be oneliner but I had to alter options for the calls after the first one for my own case
    return next ? this.getData(next, options) : Observable.empty()
  }),
  concatMap(({data}) => data)
)

obs.subscribe(
  data => mapthedata(data),
  error => error,
  complete => {
    // do something with the mapped data
  }
)
function mapthedata(data) {
  // here you should combine the data results into one, f.ex pushing to local variable
}

Upvotes: 1

CozyAzure
CozyAzure

Reputation: 8478

You can use the .expand() operator. The terminating condition for this recursion is when the next property is falsy. Use a ternary operator and the code is just one liner:

expand(({data, next}) => next ? getData(next): Observable.empty() )
    .subscribe(result => console.log(result));

Here is the working JSBin. I mocked quite a few stuffs but it should be quite trivial.

Upvotes: 4

Related Questions