0

I'm trying to design a chain of promises with a catch error at the end in my node + express app. In the example below, if any one of the 'then' functions error out I'll have to work backwards to find which one from the error message. Is there an easier way to code the catch function?

    new Promise(function(resolve, reject) {
       resolve(groupId);
    })
    .then(sid => getGuestGroup2(sid))matching carts
    .then(group =>getProducts(group))//add products
    .then(result2 =>getPrices(result2))
     .catch(error => { // (**)
        console.log('Error on GET cart.js: '+error);
        res.sendStatus(500);
      });
Nick Parsons
  • 45,728
  • 6
  • 46
  • 64
  • 2
    Error stacks contain line numbers. That should tell you exactly which part of the chain failed. Also your misuse of the promise constructor can be simplified to `Promise.resolve(groupId)`. – Patrick Roberts Aug 09 '20 at 15:28
  • 1
    It's also possible to intersperse the catches and thens if you want different error handling. In other words, it's quite handy to put catch at the end if you really want to catch everything but it's often less than desirable. – Aluan Haddad Aug 09 '20 at 15:31
  • 1
    "*find which one*" - why do you care about that? The error (message) should contain all the relevant details, I don't see what's wrong with using that. – Bergi Aug 09 '20 at 15:34
  • 1
    FYI, in the code you show, this `.then(sid => getGuestGroup2(sid))matching carts` is a syntax error. – jfriend00 Aug 09 '20 at 15:42
  • 1
    To see the full error, you need to change `console.log('Error on GET cart.js: '+error);` to `console.log('Error on GET cart.js: ', error);`. – jfriend00 Aug 09 '20 at 15:43
  • See also https://stackoverflow.com/q/26076511/1048572, https://stackoverflow.com/q/20714460/1048572, https://stackoverflow.com/q/39223854/1048572 – Bergi Aug 09 '20 at 16:43
  • Really appreciate all this great feedback! Seems adding the ,error has given me more debug output than I realized. Just wanted to make sure there wasn't something I was missing here. – Brian Kaufman Aug 09 '20 at 18:24

1 Answers1

0

The Promise chaining is generic enough to not have 'which step failed' kind of information included out of the box. You could potentially try to decode it from stack trace, but in my opinion that's way more work than its worth. Here are few options you can employ to determine which step failed.

Option 1

Set an extra property (as indicator) on the error object, which could be decoded in the catch block to determine which step in chain, the error originated from.

function predictibleErrors(promise, errorCode) {
    return Promise.resolve(promise)
        .catch(err => {
            const newErr = new Error(err.message);
            newErr.origErr = err;
            newErr.errorCode = errorCode;
            throw newErr;
        });
}

Promise.resolve(groupId)
    .then(sid => predictibleErrors(getGuestGroup2(sid), 'STEP_1')) // matching carts
    .then(group => predictibleErrors(getProducts(group), 'STEP_2')) // add products
    .then(result2 => predictibleErrors(getPrices(result2), 'STEP_3'))
     .catch(error => { // (**)
        console.log('Error on GET cart.js: '+error);

        // Check error.errorCode here to know which step failed.

        res.sendStatus(500);
      });

Option 2

Good old, catch after every step, and re-throw to skip subsequent steps.

Promise.resolve(groupId)
    .then(sid => getGuestGroup2(sid)) // matching carts
    .catch(err => {
        console.log('step 1 failed');
        err.handled = true; // Assuming err wasn't a primitive type.
        throw err;
    })
    .then(group => getProducts(group)) // add products
    .catch(err => {
        if(err.handled) { return; }
        console.log('step 2 failed');
        err.handled = true;
        throw err;
    })
    .then(result2 => getPrices(result2))
    .catch(err => {
        if(err.handled) { return; }
        console.log('step 3 failed');
        err.handled = true;
        throw err;
    })
    .catch(error => {
        // Some step has failed before. We don't know about it here,
        // but requisite processing for each failure could have been done
        // in one of the previous catch blocks.
 
        console.log('Error on GET cart.js: '+error);
        res.sendStatus(500);
    });

Please note that what we do in option two here, could also be done by the underlying methods directly. For eg. getGuestGroup2 or getProducts could include an errorCode or similar property on the error object that it throws. In this example we know step 1 or step 2 failed, but cannot know why. If the underlying methods were setting the same, they could include more accurate errorCodes with the knowledge of why the operation failed. I didn't take that route since the methods are not included in your sample and as far as I know they might not be in your control.

d_shiv
  • 1,670
  • 10
  • 8
  • Option 2 does not work. If `getGuestGroup2` rejects, it will log for each step that it failed. The nesting is inevitable. – Bergi Aug 09 '20 at 16:17
  • Btw in option 1 you forgot to `throw newErr` – Bergi Aug 09 '20 at 16:18
  • @Bergi, thanks for pointing out the miss in option 1. I've updated. The fall-through kind of behaviour would be a side-effect of the second approach. It can be avoided, if required, by incorporating some aspects of the first option. I have added one such option. – d_shiv Aug 09 '20 at 16:26