1
  • 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': 'abc'
    };
    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.

Rob
  • 14,746
  • 28
  • 47
  • 65
Robin Bajaj
  • 2,002
  • 4
  • 29
  • 47
  • On further thought I will keep the state per API call - that should solve the problem. if I open the same service in new tab, the state of the service will not be shared between those tabs - so that should be fine. – Robin Bajaj Jan 19 '18 at 15:39
  • I am still interested in knowing if the promise can carry its own state without having to use the external flags in the containing service. – Robin Bajaj Jan 19 '18 at 15:39
  • actually i simplified the above design majorly - for some reason when i initially tried out the timeout feature in the ember ajax call it was not working for me. so i got into trying the Promise.race and hence the signaling was required between the caller and callee. I revisited the Ember.$.ajaxSetup option again to set the timeout feature and its working fine for me. So i moved away from using Promise.race altogether - so no need of state sharing. But I am kinda surprised (from exercise of coding the state sharing solution) there's no easy way (public api) to inquire promise state. – Robin Bajaj Jan 20 '18 at 07:46

0 Answers0