0

I am using API call to get data and update my redux store. Following is state changes for each API call.

  1. Before making API call, set isLoading as true
  2. On success reset isLoading as false
  3. On failure reset isLoading as false and set isError as true.

My component needs data from three APIs which is stored in three different redux stores as below structure.

store: {
    book: {
        isLoading: false,
        isError: false,
        data: {}
    },
    teacher: {
        isLoading: false,
        isError: false,
        data: {}
    },
}

In my component, I use following to call api

componentWillMount() {
    const {
      loadBook,  
      loadTeacher,
    } = this.props;

 // all these load functions dispatch action which returns Promise for async API call using `fetch`
  const apiCalls = [
     loadBook(),
    loadTeacher(),
  ];

  Promise.all(apiCalls);

  }

I have written selector to check the loading state as below.

export const getIsLoading = createSelector([
  getIsBookLoading,
  getIsTeacherLoading,
],
  (bLoading, tLoading) => (
    bLoading || tLoading
  )
);

Based on value of getIsLoading I do show loading state in my component otherwise render component.

However I see problem happens when one of the API call fails. For example, loadBook fails in 100 ms and that time bLoading is changed back to false however tLoading still is true bcz loadTeacher api calls was not finished. Since Promise.all() do either all or nothing therefore API call for loadTeacher never finished therefore tLoading stas true.

Is there a way to let Promsie.all to resolve all the calls even if some failed so that it can clean dirty state?

arpl
  • 3,505
  • 3
  • 18
  • 16
joy
  • 3,669
  • 7
  • 38
  • 73
  • Yes, and I understand that's why `Promise.all()` exits. Can't change api call logic bcz its being used by many other modules. Was thinking how I can handle at my side. – joy Dec 19 '17 at 23:53

2 Answers2

0

If loadbook fails then loadTeacher won't stop, your resolve handler of Promise.all simply isn't called.

You can see in the following code that both tLoading (set false in resolve) and bLoading (set in catch) are false:

var bLoading = true;
var tLoading = true;
const loadBook = 
  () => Promise.reject("load book rejects");
const loadTeacher = 
  () => Promise.resolve("load book rejects")
  .then(()=>tLoading=false);
Promise.all([
  loadBook()
  .catch(
    (err)=>{
      bLoading=false;
      return Promise.reject(err);
    }
  ),
  loadTeacher()
])
.catch(
  ()=>console.log("bLoading:",bLoading,"tLoading:",tLoading)
);

Cannot insert as snipped because that is buggy as hell in Stack Overflow and keeps failing but you can run the code in the console.

Ash Kander is on the right track if you want to use Promise.all without failing but how do you distinguish between valid resolve values and rejects? It's better to make an explicit fail value:

const Fail = function(reason){this.reason=reason;};
Promise.all(
  [
    loadBook,
    loadTeacher
  ].map(
    fn=>
      fn()
      .catch(
        (err)=>
          new Fail(err)
      )
  )
)//this will never fail
.then(
  ([book,teacher])=>{
    console.log("did book fail?",(book&&book.constructor===Fail));
    console.log("did teacher fail?",(teacher&&teacher.constructor===Fail));
  }
)
HMR
  • 37,593
  • 24
  • 91
  • 160
-1

You have to either use a dedicated library (there are some, I dont remember the names -_-) or do it yourself - which is not so hard.

I do have some code doing it:

var MyPromise = {};

MyPromise.getDefaultPromise = function () {
    return new Promise(function (resolve, reject) { resolve(); });
};

MyPromise.when = function (promises) {
    var myPromises = [];
    for (var i = 0; i < promises.length; i++) {
        myPromises.push(MyPromise.reflect(promises[i]));
    }
    return Promise.all(myPromises).
    then(function (oResult) {
        var failure = oResult.filter(function (x) { return x.status === 'error';});
        if (failure.length) {
            return MyPromise.fail(failure);
        }
        return MyPromise.getDefaultPromise();
    });
};

MyPromise.fail = function (failure) {
    return new Promise(function (resolve, reject) {
        reject(failure);
    });
};

MyPromise.reflect = function (promise) {
    return new Promise(function (resolve) {
        promise.then(function (result) {
            resolve(result);
        }).
        catch(function (error) {
            resolve(error);
        });
    });
};

Calling MyPromise.when(-your promises array-) will ALWAYS resolve, and send you an array containing the failing promises that you can analyze in the 'then'

Ji aSH
  • 3,206
  • 1
  • 10
  • 18
  • That only works if it rejects with an object that has a member called status. It will always crash if any of the promises reject or resolve with undefined (you're trying to do `undefined.status`). The idea of non failing `Promise.all` is good but you should not do it this way (see my answer). – HMR Dec 20 '17 at 04:25