9

I am creating a script in node.js (V8.1.3) which looks at similar JSON data from multiple API's and compares the values. To be more exact I am looking at different market prices of different stocks (actually cryptocurrencies).

Currently, I am using promise.all to wait for all responses from the respective APIs.

let fetchedJSON =
        await Promise.all([getJSON(settings1), getJSON(settings2), getJSON(settings3) ... ]); 

However, Promise.all throws an error if even just one promise rejects with an error. In the bluebird docos there is a function called Promise.some which is almost what I want. As I understand it takes an array of promises and resolves the two fastest promises to resolve, or otherwise (if less than 2 promises resolve) throws an error.

The problem with this is that firstly, I don't want the fastest two promises resolved to be what it returns, I want any successful promises to be returned, as long as there is more than 2. This seems to be what Promise.any does except with a min count of 1. (I require a minimum count of 2)

Secondly, I don't know how many Promises I will be awaiting on (In other words, I don't know how many API's I will be requesting data from). It may only be 2 or it may be 30. This depends on user input.

Currently writing this it seems to me there is probably just a way to have a promise.any with a count of 2 and that would be the easiest solution. Is this possible?

Btw, not sure if the title really summarizes the question. Please suggest an edit for the title :)

EDIT: Another way I may be writing the script is that the first two APIs to get loaded in start getting computed and pushed to the browser and then every next JSON that gets loaded and computed after it. This way I am not waiting for all Promises to be fulfilled before I start computing the data and passing results to the front end. Would this be possible with a function which also works for the other circumstances?

What I mean kind of looks like this:

Requesting JSON in parallel...

|-----JSON1------|

|---JSON-FAILS---| > catch error > do something with error. Doesn't effect next results.

|-------JSON2-------| > Meets minimum of 2 results > computes JSON > to browser.

|-------JSON3---------| > computes JSON > to browser.

Manu Masson
  • 1,667
  • 3
  • 18
  • 37

4 Answers4

5

How about thening all the promises so none fail, pass that to Promise.all, and filter the successful results in a final .then.

Something like this:

function some( promises, count = 1 ){

   const wrapped = promises.map( promise => promise.then(value => ({ success: true, value }), () => ({ success: false })) );
   return Promise.all( wrapped ).then(function(results){
      const successful = results.filter(result => result.success);
      if( successful.length < count )
         throw new Error("Only " + successful.length + " resolved.")
      return successful.map(result => result.value);
   });

}
pishpish
  • 2,574
  • 15
  • 22
1

This might be somewhat clunky, considering you're asking to implement an anti-pattern, but you can force each promise to resolve:

async function fetchAllJSON(settingsArray) {
  let fetchedJSON = await Promise.all(
    settingsArray.map((settings) => {
      // force rejected ajax to always resolve
      return getJSON(settings).then((data) => {
        // initial processing
        return { success: true, data }
      }).catch((error) => {
        // error handling
        return { success, false, error }
      })
    })
  ).then((unfilteredArray) => {
    // only keep successful promises
    return dataArray.filter(({ success }) => success)
  })
  // do the rest of your processing here
  // with fetchedJSON containing array of data
}
Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
1

You can use Promise.allSettled([]). the difference is that allSettled will return an array of objects after all the promises are settled regardless if successful or failed. then just find the successful o whatever you need.

  let resArr = await Promise.allSettled(userNamesArr.map(user=>this.authenticateUserPassword(user,password)));
        return resArr.find(e=>e.status!="rejected");

OR return resArr.find(e=>e.status=="fulfilled").

0

The other answers have the downside of having to wait for all the promises to resolve, whereas ideally .some would return as soon as any (N) promise(s) passes the predicate.

let anyNPromises = (promises, predicate = a => a, n = 1) => new Promise(async resolve => {
 promises.forEach(async p => predicate(await p) && !--n && resolve(true));
 await Promise.all(promises);
 resolve(false);
});

let atLeast2NumbersGreaterThan5 = promises => anyNPromises(promises, a => a > 5, 2);

atLeast2NumbersGreaterThan5([
 Promise.resolve(5),
 Promise.resolve(3),
 Promise.resolve(10),
 Promise.resolve(11)]
).then(a => console.log('5, 3, 10, 11', a)); // true

atLeast2NumbersGreaterThan5([
 Promise.resolve(5),
 Promise.resolve(3),
 Promise.resolve(10),
 Promise.resolve(-43)]
).then(a => console.log('5, 3, 10, -43', a)); // false

atLeast2NumbersGreaterThan5([
 Promise.resolve(5),
 Promise.resolve(3),
 new Promise(() => 'never resolved'),
 Promise.resolve(10),
 Promise.resolve(11)]
).then(a => console.log('5, 3, unresolved, 10, 11', a)); // true
junvar
  • 11,151
  • 2
  • 30
  • 46