jeudi 18 janvier 2018

Ember (Javascript) - how to signal the caller about the state of a promise

  • I have Ember code where the backend API calls are abstracted into a separate service. This service uses ember-ajax library for making backend calls.
  • This service sets up the common headers, handles the timeout errors, and 4xx/5xx errors. And anything else like 422 (validation errors) are left to be handled by the calling code.

-

 getCustomerProfile (authenticationToken) {
         const backendService = this.get('callBackendService');
         return backendService.callEndpoint(GET_METHOD,
             getCustomerProfileAPI.url,
             {'X-Auth-Token': authenticationToken}).then((customerProfileData) => {
                if (!backendService.get('didAnybodyWin') && customerProfileData) {
                   backendService.set('didAnybodyWin', true);
                   return customerProfileData.profiles[0];
                }
             }).catch((error) => {
                if (isInvalidError(error)) {
                   if (!backendService.get('didAnybodyWin')) {
                      backendService.set('didAnybodyWin', true);
                      backendService.transitionToErrorPage();
                      return;
                   }
                } 
          });
    }

and the call-backend-service looks like this

  callEndpoint (httpVerb, endPoint, headersParameter, data = {}, 
   timeoutInMillisecs = backendCallTimeoutInMilliseconds) {
    const headersConst = {
        'Content-Type': 'application/vnd.api+json',
        'Accept': 'application/vnd.api+json',
        'Brand': 'cibc'
    };
    var headers = Ember.assign(headersParameter, headersConst);

    var promiseFunctionWrapper;

    this.set('didAnybodyWin', false);
    if (httpVerb.toUpperCase() === GET_METHOD) {
        Ember.Logger.warn('hit GET Method');
        promiseFunctionWrapper = () => {
            return this.get('ajax').request(endPoint, {headers});
        };
    } else if (httpVerb.toUpperCase() === POST_METHOD) {
        Ember.Logger.warn('hit POST Method');
        promiseFunctionWrapper = () => {
            return this.get('ajax').post(endPoint, {data: data, headers: headers});
        };
    } 

    return RSVP.Promise.race([promiseFunctionWrapper(), this.delay(timeoutInMillisecs).then(() => {
        if (!this.get('didAnybodyWin')) {
            this.set('didAnybodyWin', true);
            Ember.Logger.error('timeout of %s happened when calling the endpoint %s', backendCallTimeoutInMilliseconds, endPoint);
            this.transitionToErrorPage();
            return;
        }
    })]).catch((error) => {
        if (!this.get('didAnybodyWin')) {
            if (isTimeoutError(error)) {
                this.set('didAnybodyWin', true);
                Ember.Logger.warn('callBackEndService: isTimeoutError(error) condition is true');
                this.transitionToErrorPage();
                return;
            } else if (isAjaxError(error) && !isInvalidError(error)) { //handles all errors except http 422 (inValid request) 
                    this.set('didAnybodyWin', true);
                    Ember.Logger.warn('callBackEndService: isAjaxError(error) && !isInvalidError(error) [[ non timeout error]] condition is true');
                    this.transitionToErrorPage();
                    return;

            } else {
                throw error;  // to be caught by the caller
            }
        }
    });
},

The callEndpoint does a RSVP.Promise.race call to make sure the called backend API comes back before a timeout happens. It runs two promises and whichever resolves first is the one that wins. didAnybodyWin is the flag that guards both the promises from getting executed.

Up to this part is all fine.

But this didAnybodyWin becomes the shared state of this call-backend-service because it has to convey back to the caller whether it ran the default set of then or catch blocks or does it expect the caller to run its then/catch blocks.

The problem is when model() hook is run, I am doing

RSVP.all {
  backendAPICall1(),
  backendAPICall2(),
  backendAPICAll3()
}

This RSVP.all is going to execute all 3 calls one after another, so they will hit the call-backend-service in an interleaved fashion and hence run the risk of stepping over each other (when it comes to the didAnybodyWin shared state).

How can this situation be avoided ? Is there any other better way for the callee to signal to the caller whether or not its supposed to do something with the returned promise.




Aucun commentaire:

Enregistrer un commentaire