0

I need to fetch entity details, lets say book from REST endpoint. The book object might look like this

{
  title: XYZ,
  published: XYZ
  author: URL_TO_ENDPOINT
  publisher: URL_TO_ENDPOINT
}

Now I need to chain promises so that I get the author and publisher data as well. I have simple ApiService that implements getResourceByURL method, and I created new service DetailsService that calls my ApiService and chains promices This is easy as long as I know exactly my object:

return this.apiService.getResourceByURL(book.url).flatMap((entity) => {
  return Observable.forkJoin(
    Observable.of(entity),
    this.apiService.getResourceByURL(entity.author)
    this.apiService.getResourceByURL(entity.publisher)
  ).map(entityFullDetails => {
    let entity = entityFullDetails[0];
    let author = entityFullDetails[1];
    let publisher = entityFullDetails[2];
    entity.author = author;
    entity.publisher = publisher;
    return entity;
  });
});

problem is that I wanted to use the same method of DetailsService to be able to fetch details for authors/publishers as well, meaning that it would need to iterate over all keys of entity object and call apiService.getResourceByURL if value of that key is an URL (lets say it starts with http).

Can that be easily done? or should I lower the abstraction level and prepare separate methods to get BookDetail, AuthorDetail, etc.

I am interested only in one level nesting. meaning that if I fetch book's author I do not need to get all books written by that author meaning I am fine getting something like

{
  title: XYZ,
  published: XYZ
  author: {name: XXX, country: XXX, books: [URL, URL, URL], ...}
  ...
}

to prevent looping

Chaki
  • 73
  • 6
  • Why are you talking about promises when you are only working with observables? – Jota.Toledo Dec 26 '17 at 11:35
  • Possible duplicate of [Angular 2 - Chaining http requests](https://stackoverflow.com/questions/42626536/angular-2-chaining-http-requests) – Jota.Toledo Dec 26 '17 at 11:37
  • @Jota.Toledo Doesn't strike me as a duplicate, at least not to that question. OP here knows how to chain requests, it's just about automating the process without having to know the structure of the returned resource. – Ingo Bürk Dec 26 '17 at 11:53
  • Sorry used to angularJS so I sometimes still say/write 'promise' instead of 'observable' :) – Chaki Dec 26 '17 at 16:03

1 Answers1

0

You can just write a function that does the necessary things automatically. It does actually work recursively, but if you want to specifically avoid this, just replace the recursive call to itself by a call to getResourceByURL.

const createFakeResponse = url => {
  switch (url) {
    case 'http://book': return { 
      name: 'The rxjs Book', 
      author: 'http://author',
      publisher: 'http://publisher',
    };

    case 'http://author': return { 
      name: 'John Doe',
      numberOfBooks: 'http://numberOfBooks'
    };

    case 'http://publisher': return 'ACME Publishing Corp.';
    case 'http://numberOfBooks': return 42;
  }
};

// Fake the HTTP calls with some delay
const getResourceByURL = url => Rx.Observable
  .of(createFakeResponse(url))
  .do(() => console.log('HTTP call: ', url))
  .delay(1000 * Math.random());

// Determines whether this key in the resource has to be fetched from its URL
const isReference = ([key, value]) => value.startsWith('http://');

const fetchWithReferences = url => getResourceByURL(url)
    .do(resource => console.log('Raw resource: ', resource))
    .flatMap(resource => {
      // Find all keys for which the corresponding value is
      // a reference that we need to resolve.
      const referenceKeys = Object.entries(resource)
        .filter(isReference)
        .map(([key]) => key);

      return Rx.Observable
        .forkJoin(
           Rx.Observable.of(resource),
           ...referenceKeys.map(key => fetchWithReferences(resource[key]))
         )
        .map(([resource, ...resources]) => Object.assign(resource, ...resources.map((current, index) => {
          // This uses computed property names
          return { [referenceKeys[index]]: current };
        })));
    });

console.log('Starting...');
fetchWithReferences('http://book')
  .map(result => JSON.stringify(result, null, 2))
  .subscribe(result => console.log('Final result: ', result));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.min.js"></script>
Ingo Bürk
  • 19,263
  • 6
  • 66
  • 100