3

I have to call one api, then use the response to call others api, let's simplify the problem, assume we only call 2 APIs for this case.

My problems:

1. I'm confused how to handle try catch in this case.

2. Promises won't stop, so how do I stop them? (I found a solution using bluebird) Why do I need to stop them? because says it's more APIs then it's a waste to proceed.

My attempt code is below

async function getUserFriendsAndFriendOfFriend() {
  let user = await fetchUser('/users/me')

  let friendsIDs = await fetchJSON(`/friends/${user.id}`) //use user.id as param
  let friend_of_friend_IDs = await fetchJSON(`/friends_of_friends/${user.id}`) //use user.id as param

  //build promises
  let promises = friendIDs.map(id => fetchJSON(`/users/${id}`))
  promises = friend_of_friend_IDs.map(id => fetchJSON(`/users/${id}`))

  try {
    await Promise.all(promises) //is this correct?
  } catch(e) {
    alert(e) // maybe either one promises failed, but it won't stop other from calling
  }
}

getUserFriendsAndFriendOfFriend() // call

I have this thought when I was watching this video https://www.youtube.com/watch?v=NsQ2QIrQShU

My code above actually a modified version from his, at minute 26:10.

Sharon Chai
  • 507
  • 1
  • 6
  • 20
  • Can you describe what you wish to happen? (where should the promise stop or what promises doesn't cached? – Tom Mendelson Mar 13 '18 at 08:27
  • Yeah, `async` / `await` uses try catch to catch the errors. If you really want to stop after one error occured, you will have to `await` it instead of creating the `map` of `friendsIDs`. Note that your second mapping will replace the first one, so the calls are already long time running. Your `await` on the `promise.all` will only await on the friends of friends map – Icepickle Mar 13 '18 at 08:28
  • So, what I wanted to say, if an error occurs when calling the friends, it will not land in the catch, only the error that might occur in the friends of friends will be caught – Icepickle Mar 13 '18 at 08:37
  • only the friends of friends would occur beacus the friends request replaced, try to change you code to: let promises = friendIDs.map(id => fetchJSON(`/users/${id}`)).concat(friend_of_friend_IDs.map(id => fetchJSON(`/users/${id}`))) that way, you would have both of your request inside your promise list – Tom Mendelson Mar 13 '18 at 08:39
  • @TomMendelson yeah you're right, but I found a more elegant wait to do: `await Promise.all([p1, p2])` – Sharon Chai Mar 13 '18 at 09:09
  • @TomMendelson but I still haven't find a way to stop the rest of the promise, try catch will handle the error but it will still execute the calls. – Sharon Chai Mar 13 '18 at 09:10
  • When do you wish to stop the calls? if 1 fails? – Tom Mendelson Mar 13 '18 at 09:22
  • The standard currently still does not define any possibility to cancel a Promise ([is it possible to force cancel a promise](https://stackoverflow.com/questions/30233302)), as of that you need to implement that logic yourself. – t.niese Mar 13 '18 at 09:52

1 Answers1

1

Concerning the error handling

I guess it depends on how you want to handle the errors that might occur. Do you have a concept for error handling in your code?

It might not be the best to simply "handle" the errors, while letting the getUserFriendsAndFriendOfFriend pretend that it succeeded.

Personally, I would rather choose to let the error be thrown, and the consumer of your method should handle any possible errors.

Of course it could be that you want to log the errors that have occurred, but you should still re-throw it. If you do not, then the consumer cannot know if the method has failed or not?

How to stop the promises

Well, in your current version, you cannot really stop the promises, because they are all running already. You initiate all of them at the moment you are mapping the 2 arrays (this starts your internal fetch).

The Promise.all will then complete or fail in the end (though note, in your version of the code added to the question, you will only catch errors occurring on the friend_of_friend_IDs array.

If you really want to make them fail as soon as the first one fails, then you can rather build a chain for them, by changing your code like:

await friendIDs
  .concat( friend_of_friend_IDs )
  .reduce( 
    (current, item) => current.then( 
      () => fetchJSON( `/users/${item}` ) ), 
    Promise.resolve( true ) );

this would first combine the 2 arrays, and then resolve in sequence the friends list. As soon as one would fail, the chain would stop, no other promises would be called anymore.

One problem with this approach would be that you wouldn't have the possibility to capture the results, but to do that you could theoretically just fill an array with the results, like for example in the following way:

let results = [];
return await friendIDs
  .concat( friend_of_friend_IDs ) // concatenate the 2 arrays
  .reduce( (current, item) => current.then( // chain the promise
    () => fetchJSON( `/users/${item}` )
      .then( response => results.push( response ) ) // handle the results
    ), 
    Promise.resolve( true ) ) // start with a default promise
  .then( () => results ); // return the results

now the results would contain all of the found friends, and that would be given out to the consumer of your method. Any errors that are fired, must be handled by the consumer, so the eventual code would look something like

async function getUserFriendsAndFriendOfFriend() {
  let user = await fetchUser('/users/me');

  let friendsIDs = await fetchJSON(`/friends/${user.id}`);
  let friend_of_friend_IDs = await fetchJSON(`/friends_of_friends/${user.id}`);

  let results = [];
  return await friendIDs
    .concat( friend_of_friend_IDs ) // concatenate the 2 arrays
    .reduce( (current, item) => current.then( // chain the promise
      () => fetchJSON( `/users/${item}` )
        .then( response => results.push( response ) ) // handle the results
      ), 
      Promise.resolve( true ) ) // start with a default promise
    .then( () => results ); // return the results
}

getUserFriendsAndFriendOfFriend()
  .then( results => {
    // results contains all the responses from the calls to fetchJSON with users id
  })
  .catch( err => {
    // error will show you what went wrong with the single request, no other results will be returned
  });
Icepickle
  • 12,689
  • 3
  • 34
  • 48
  • Many thanks for this! just need one catch to handle the error, thanks for your enlighting! – Sharon Chai Mar 14 '18 at 01:53
  • actually to replace reduce I can use ``for of for(let friend_id of friend_of_friend_IDs) { const response = await fetchJSON(`users/${item}`); results.push( response ) }`` it's more clean in my opinion. What do you think? – Sharon Chai Mar 14 '18 at 01:57
  • @SharonChai Yeah, that's perfectly fine as well, in the end it will do the same (don't forget to combine both arrays) – Icepickle Mar 14 '18 at 08:53
  • I'm stuck with another problem using async await, can you help? https://stackoverflow.com/questions/49272045/refactor-promise-and-promise-all-with-async-await-of-es2017 – Sharon Chai Mar 14 '18 at 09:18
  • @AmerllicA feel free to tell me why you found it to long, so I could put a disclaimer in it ;) – Icepickle May 16 '18 at 14:10