Search code examples
javascriptecmascript-6es6-promise

Catch all block in nested promises


Is there a way to add a catch all block to run after an entire promise chain? In the example below, get is a generic lib method that handles some data processing and returns the data to callGet which takes the data and handles the UI in whatever way. I want to make a catch all error handling block that runs after the entire chain runs. I want the catch all finally block to live on the get block because it is the generic lib method.

// generic lib method used throughout codebase
function get(url, data) {
    return axios.get(url, data).then(res => {
        // handle response in whatever way (1)
    }).catch(error => {
        // handle error in whatever way (2)
    }).finally(() => {
        // RUN BLOCK AFTER callGet CHAIN (3)
    });
}

// specific call from somewhere within the codebase
function callGet() {
    get('/test', {}).then(res => {
        // do more with response from get (4)
    }).catch(err => {
        // handle error in UI somehow (5)
    });
}

callGet();

If this were a single chain written out, the order would run like this: (I know this isn't how promises work - just pseudocode to write out the thoughts)

axios.get(url, data).then(res => {
    // (1) then in get block
}).then(res => {
    // (4) then in callGet block
}).catch(err => {
    // (2) catch in get block if errors in get block
}).catch(err => {
    // (5) catch in callGet block if errors in callGet block
}).finally(() => {
    // (3) run after callGet block
})

Update:

Ultimately ended up going with bluebird's onPossiblyUnhandledRejection http://bluebirdjs.com/docs/api/promise.onpossiblyunhandledrejection.html

It ends up doing what I need it to to catch any unhandled rejections that the dev isn't specifically handling.


Solution

  • What you want is not possible in the way you want it.

    The reason is simple: the hierarchy of those .thens is different.

    To make it easier to understand, imagine, that it's all synchronous (it isn't, and shouldn't be; that's just a thought experiment). Without the catch and finally, it would look like this:

    function callGet(){
      try{
      // vvvvvvvvvvvv--- for sake of simplicity, assume that it's here (although it isn't)
        (function get(...){
          axios.get(...)
          // (1) then in get block
        })(...)
        // (4) then in callGet block
      }catch(err){
        // (5) catch in callGet block if errors in callGet block
      }
    }
    

    To add the catch and finally, you'd have to change it into something like this:

    function callGet(){
      try{
        try{
          try{
          // vvvvvvvvvvvv--- for sake of simplicity, assume that it's here (although it isn't)
            (function get(...){
              axios.get(...)
              // (1) then in get block
            })(...)
            // (4) then in callGet block
          }catch(err){
            // (2) catch in get block if errors in get block
          }
        }catch(err){
          // (5) catch in callGet block if errors in callGet block
        }
      }finally{
        // (3) run after callGet block
      }
    }
    

    That's all nice, and actually, it would work, but (unfortunately, there is a "but") now just try to move the catch (2) and the finally (3) into the get function. There's clearly no way to do so (without messing up their order)!

    With promises, there would be a very hacky and complicated way to do something similar to it, but just let it alone, and let me explain why you shouldn't do that.


    Every function should do its own job. Preferably one job. Your get function has nothing to do with the UI, etc. (at least it shouldn't have). Its job is to make a network request, maybe process the response and handle errors. However, it should only handle its own errors, but not its caller's.

    If it were in the way you want, the catch of get (2) would also capture and handle the errors thrown in callGet (4), while leaving only the errors thrown from that catch handler (2) for callGet's catch (5).

    That would make your code complicated, unsemantic, unreliable, and therefore hard to maintain.

    The best you can do is to either handle the error by yourself in get, or to leave the error handling to the caller and don't try to mess up with your caller's job. If you're writing a library, you may offer helper methods for the caller, that composes promises in some unique way, but it's up to the caller to decide whether to use it or not.

    If you want to enforce that anyway, you might use a different programming pattern for that (I'm not the good person to ask which), JS is flexible enough to let you implement it.