0

I am fetching URLs specified in the array and then combine fetched results. I want to ignore failed fetches.

While are tons of post on the subject:

  1. Wait until all promises complete even if some rejected
  2. https://gist.github.com/nhagen/a1d36b39977822c224b8

I just can't figure our how to apply this to my code, that fetches URLs from the array:

    Promise.all (arrayOfBlobs.map (x => fetch (x).then (response => response.json())) )         
    .then (json => {
                json.forEach ( x => { 
                    if (Array.isArray (x)) {
                        // this json has array of objects
                        console.log (`Received ${x.length} prospects`)
                        x.forEach ( y => combinedArray.push (y) )   
                    }   
                    else {
                        // this json has single prospect object
                        console.log (`Received single prospect`)
                        combinedArray.push (x)
                    }
                })
        this.setState({loadingTable: false, data: combinedArray})
    })  
    .catch (error => {
        console.error (error.message)
        this.setState({loadingTable: false, data: combinedArray})
    })

For example below did not work:

Promise.all (arrayOfBlobs.map (x => fetch (x).then (response => response.json())) )         
            .then (json => {
                        json.forEach ( x => { 
                            if (Array.isArray (x)) {
                                // this json has array of objects
                                console.log (`Received ${x.length} prospects`)
                                x.forEach ( y => combinedArray.push (y) )   
                            }   
                            else {
                                // this json has single prospect object
                                console.log (`Received single prospect`)
                                combinedArray.push (x)
                            }
                        })
                        .catch (e => {console.log (`Failed to fetch due to ${e.message}`)})
                this.setState({loadingTable: false, data: combinedArray})
            })  
            .catch (error => {
                console.error (error.message)
                this.setState({loadingTable: false, data: combinedArray})
            })

What do I need to do to modified my code so failed fetches are ignored?

leon
  • 1,412
  • 2
  • 17
  • 26
  • Try something like `fetch (x).then(response => response.json()).catch(() => "mark as failed")` and filter afterwards. – pishpish Nov 03 '19 at 18:43

3 Answers3

3

Use Promise.allSettled() it will return that status of fulfilled/rejected with the value.

More info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled

Matt Aft
  • 8,742
  • 3
  • 24
  • 37
  • This rocks! Thanks you both: Matt and Begri. I can now see how to make either of this work – leon Nov 03 '19 at 19:08
1

The catch is supposed to go on the individual fetch promise inside the map:

Promise.all(arrayOfBlobs.map(x =>
    fetch(x)
    .then(response => response.json())
    .catch(e => {
//  ^^^^^^^
        console.log(`Failed to fetch due to ${e.message}`);
        return null; // this value will get into the `json` array
    })
))         
.then(json => {
    const combinedArray = [];
    for (const x in json) { 
        if (Array.isArray(x)) {
            // this json has array of objects
            console.log (`Received ${x.length} prospects`)
            combinedArray.push(...x);
        } else if (x != null) {
            // this json has single prospect object
            console.log (`Received single prospect`)
            combinedArray.push(x)
        } else { // x == null
            console.log(`(ignored error)`)
        })
    }
    this.setState({loadingTable: false, data: combinedArray})
})

Also don't forget to handle http (status code) errors!

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Yes, it did work! I actually tried it to, however the result was not what I have expected, hence I thought I was wrong. This approach put "undefined" into the combined array. Yes, I can filter array and remove undefined values. However I was hopping for a more elegant solution: when promise failed, it's results are completely ignored and are not added to the combined array. I suspect I do need to filter promises and reject failed promises, as pointed our for this to work. but I guess filtering array is easier. – leon Nov 03 '19 at 18:54
  • There's no way to do it without filtering the result, neither with `Promise.all` nor with `Promise.allSettled`. – Bergi Nov 03 '19 at 19:01
0

A version of Promise.allSettled that removes rejected results:

function allFullfilled(promises) {

  const responses = [];
  let settledCount = 0;

  return new Promise(res => {

    for(const promise of promises) {

      promise
        .then( promiseResult => {
          responses.push(promiseResult);
        })
        .catch(err => {
          // ignore
        })
        .then( () => {
          settledCount++;
          if (settledCount === promises.length) {
            res(responses);
          }
        });
    }

  });

}
Evert
  • 93,428
  • 18
  • 118
  • 189
  • Here is what I did to handle only un-rejected promises with real values: if ( ( typeof (x.value) !== "undefined") && (x.status !== "rejected") ) { – leon Nov 03 '19 at 19:43