Multiple RxJS AJAX Requests

2019-09-11 04:34发布

问题:

I'm using rxjs to make several http requests and I want to end up with an object that looks something like:

{
  100: {
    ...response from api call...
  },
  205: {
    ...response from api call...
  },
  ...etc...
}

Here's what I have so far:

const projectIds = [100, 205, 208, 300]
const source = Rx.Observable
  .from(projectIds)
  .flatMap(id => get(`projects/${id}/builds`))
  .map(response => response['data'])
  .zip(projectIds)
  .toArray()

source.subscribe(pipelines => {
  console.log(pipelines)
})

This gives me back an array of arrays where the first element is the response from the call and the second element is the id of the project.

The problem is that the response doesn't match the project id as the responses come back in different orders depending on which request completes first.

How can I preserve the order (or at least know which projectId goes with each response) while also ending up with an object at the end (currently is an array)?

回答1:

Just use the flatMap with elementSelector overload:

.flatMap(
  projectId => getProjectDetails(projectId),
  (projectId, details) => ({ id: projectId, details })
)

function getProjectDetails(id){
  return get(`projects/${id}/builds`)
    .map(response => response['data']);
}

This will let you combine the input argument and every output value from flatMap as you require, effectively preserving context. If you require the output order to stay the same you can use .concatMap but then all emissions are done after each other instead of concurrently.

Then finally use a .reduce to combine all objects back to one big emission:

.reduce((acc, curr) => acc[curr.id] = curr.details, {})


回答2:

Option 1

Instread of flatMap you could use concatMap, that should preserve the order.

Note: This won't make any concurrent requests though, if that is what you are looking for.


Option 2

If you want to make concurrent requests (at least from the RxJS side, depending on the browser this could still be limited) you could use some construct using forkJoin like the following:

const projectIds = [100, 205, 208, 300]
const source = Rx.Observable
  .from(projectIds)
  .map(id => get(`projects/${id}/builds`).pluck('data'))
  .toArray()
  .switchMap(requestArray => Rx.Observable.forkJoin(requestArray))
  .zip(projectIds)


source.subscribe(pipelines => {
  console.log(pipelines)
})