0

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.

Robby Kim
  • 454
  • 5
  • 20
  • 1
    You're going to have to be more specific here because what it seems like you're asking for doesn't make any sense. `callGet()` calls `get()`, but you're asking for `get()` to do something when `callGet()` is done. That doesn't make any sense. `get()` is the child and the child has no way of knowing when the parent (the caller) is done with whatever it's doing. All the child `get()` knows is when it is done. It knows nothing about its caller. – jfriend00 Dec 15 '20 at 00:20
  • 1
    Presumably, you shouldn't be organizing things this way. Or, you need to create some event that `get()` can register some code for that `callGet()` will emit when it's done and you pass in the emitter to `get()` so it can hook up to it. With real code (not pseudo-code), we could probably suggest a better code organization, but there is no answer to the theoretical question you pose because that's not possible. `get()` doesn't know anything about it's caller. – jfriend00 Dec 15 '20 at 00:21
  • You're probably looking for the [promise disposer pattern](https://stackoverflow.com/questions/28915677/what-is-the-promise-disposer-pattern) – Bergi Dec 15 '20 at 16:13
  • @jfriend00 essentially what i'm looking for is a catch all for unhandled rejections that the dev might miss so the default behavior would be to redirect to a static error page. i ended up going with bluebird's onPossiblyUnhandledRejection http://bluebirdjs.com/docs/api/promise.onpossiblyunhandledrejection.html – Robby Kim Dec 15 '20 at 18:14
  • Well, your question would have gotten a number of on-topic answers if that's actually the question you asked. Nowhere in your question does it say that's what you wanted. – jfriend00 Dec 15 '20 at 20:30
  • yep, and that's my fault. i couldn't think of the right words to describe what i was looking until i did more digging. apologize for the unspecific question – Robby Kim Dec 15 '20 at 21:09

2 Answers2

1

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.

FZs
  • 16,581
  • 13
  • 41
  • 50
-1

Just rethrow the error in the first catch block if you want to handle it further in the following catch chains;

function get(url, data, onFulfilledHook, onRejectedHook) {
    return axios.get(url, data).then(res => {
        // handle response in whatever way (1)
    })
     .then(onFulfilledHook)
     .catch(error => {
        // handle error in whatever way (2)

        throw error; // rethrow the error <<<<<<<<<<<
    })
    .catch(onRejectedHook)
    .finally(() => {
        // RUN BLOCK AFTER callGet CHAIN (3)
    });
}

function callGet() {
    get('/test', {}, res => {
        // do more with response from get (4)
    }, err => {
        // handle error in UI somehow (5)
    });
}
Dmitriy Mozgovoy
  • 1,419
  • 2
  • 8
  • 7
  • That won't do the desired result. I need it to run the `finally` block *after* the catch in the `callGet` method. I'm not worried about the catch method being called. – Robby Kim Dec 14 '20 at 23:09
  • 2
    You can pass `then` and `catch` functions as callbacks to the get function. But I'm pretty sure the problem is bad architecture. – Dmitriy Mozgovoy Dec 15 '20 at 00:24
  • Agree with Dmitriy, even if you get that working the code will not be understandable. Usually the better approach is to let the callers decide how they want to handle the errors, if there is a lot duplicate code you could create a function that is invoked by the callers in their catch blocks. – cppNoob Dec 15 '20 at 00:47