javascriptpromisesoftware-design

Stop propagating promises (not rejecting them)


I have an HttpClient class with a "request" method that is responsible for making requests to an API and returning a promise and I call this method from the different services, where I manage the returned promises.

The problem is that if HttpClient receives a session expired error, I automatically redirect the user to the login page from HttpClient, but since a promise is always returned from the "request" method, the code from which httpClient.request was called continues to execute and it displays the snackbar error to the user.

Is there a way to ensure that once it is redirected to the login nothing else is executed? (Note that since it is a SPA (I'm using Vue with Nuxt.js) the page is not reloading so the code execution is not interrupted. Also if I try to use location.href to refresh the whole page, the errors are displayed a moment before than the page refreshes)

The idea is not to have to manage a rejected promise for expired session in each service, since I would be rewriting the same code in all services or even in functions that call the services, since many services also return a promise, so I would have to manage those promises again and again.

I guess there has to be some better way to handle this, but I don't know how.

Here is my current HttpClient request method implementation (summarized for better understanding):

export default class HttpClient {
  [...]

  /**
  * Sends a request to the API and returns a promise with the result or an error.
  *
  * @param requestOptions
  * @return Promise
  */
  public async request(params) {
    [...]

    // Send request
    try {
      const response: ApiResponse = await $fetch(fetchUrl.toString(), requestOptions);

      if (this.hasError(response)) {
        const errorMessage = this.getErrorMessage(response as ErrorApiResponse);
        if (await this.checkSessionExpired(errorMessage)) return Promise.reject(new SessionExpiredError(errorMessage));
        return Promise.reject(new ApiHandledError(errorMessage));
      }

      return Promise.resolve(this.getData(response as SuccessApiResponse));
    } catch (error) {
      return Promise.reject(error);
    }
  }
}

I know there are several questions related to stopping promise propagation, but none of them are answering my problem since most of the answers recommend to reject the promise which doesn't work for me since this displays an error snackbar to the user in my app.


Solution

  • Just wrap your request into a promise and don't resolve it.

    All allocated resources before calls will be eventually released, test several times and eventually you will see them released.

    class HttpClient {
      request(callId) {
        return new Promise(async(resolve, reject) => {
          try{
            const response = await new Promise((resolve, reject) => {
              if(Math.random() > .33) {
                setTimeout(() => resolve(Math.random() > .5 ? {error:'session expired'} : {status: 'ok'}), 1000);
              }else{
                setTimeout(() => reject(new Error('some api error has happenned')), 1000);
              }
            });
            if(response.error === 'session expired'){
              console.log(callId + ': session expired, redirecting to login route'); 
              return;
            }
            resolve(response);
          }catch(e){
            reject(e);
          }
        });
      }
    }
    
    const http = new HttpClient;
    const registry = new FinalizationRegistry(obj => {
      console.log(obj, 'released');
    });
    let callId = 0;
    async function test(){
      const id = ++callId;
      try{
        const obj = {test: 'memory leak '.repeat(10000000)};
        registry.register(obj, 'test object ' + id);
        const response = await http.request(id);
        console.log(id + ': data received:', response);
      }catch(e){
        console.log(id + ': request error:', e.message);
      }
    }
    <button onclick="test()">test http request</button>
    <div>it will give 3 random outcomes</div>