1

I'm not sure if "fail-fast" is the best way to describe this methodology, but ever since I started to learn about programming I have always been taught to design functions like this:

function doSomething() {
    ... // do error-prone work here

    if (!allGood) {
        // Report error, cleanup and return immediately. Makes for cleaner,
        // clearer code where error-handling is easily seen at the top
        ...
        return;
    }

    // Success! Continue on with (potentially long and ugly) code that may distract from the error
}

As such, I'm trying to call a promisified function like so:

doSomethingAsync(param).catch(err => {
    console.error(err);
}).then(() => {
    // Continue on with the rest of the code
});

But this gives me behaviour akin to the finally block of a classic try...catch...finally statement, i.e. the then() block will always be called, even after an error. Sometimes this is useful, but I rarely find myself needing such functionality (or try...catch statements in general, for that matter).

So in the interest of failing as quickly and clearly as possible, is there a way that I can make the second example above work in the way that I expect (i.e. then() is only executed if catch() wasn't, yet a single catch() will still catch all errors raised by doSomethingAsync())?

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
Kenny83
  • 769
  • 12
  • 38
  • Move your catch after the then, otherwise it restores the promise with whatever you return from the then. But now that if there's a promise, it's async, so returning values don't mean anything, unless you return a new promise from within the chain, or use async await to make it easier – Ruan Mendes Jan 09 '20 at 02:12
  • 1
    @JuanMendes An issue with that is that the `.catch` will catch errors that occur inside the `.then`, whereas OP wants to catch errors only from `doSomethingAsync(param)` (and OP would rather have the catch logic close to the `doSomethingAsync` call, rather than after it and its `.then`) – CertainPerformance Jan 09 '20 at 02:13
  • @certain If you don't want errors on the then to be passed to catch, you can pass a second parameter to then as the error callback – Ruan Mendes Jan 09 '20 at 02:17
  • Here's an article explaining that behavior https://www.codingame.com/playgrounds/347/javascript-promises-mastering-the-asynchronous/the-catch-method The OP should understand this – Ruan Mendes Jan 09 '20 at 02:19
  • Errors in promises don't appear as error conditions that you will need to test for (like `if (!allGood)`, but they rather work like exceptions. Fail-fast is the default if you just don't do anything, just like when the "*error-prone work*" throws an exception. – Bergi Jan 09 '20 at 12:51

1 Answers1

1

If you use async and await instead of .then, you can effectively wait for the Promise to resolve (or reject), and if it rejects, return early:

(async () => {
  try {
    await doSomethingAsync(param);
  } catch(err) {
    console.error(err);
    return;
  }
  // Continue on with the rest of the code
})();

const doSomethingAsync = () => new Promise((resolve, reject) => Math.random() < 0.5 ? resolve() : reject('bad'));

(async () => {
  try {
    await doSomethingAsync();
  } catch(err) {
    console.error(err);
    return;
  }
  console.log('continuing');
})();

That's what I'd prefer. You can also use the .then(onResolve, onReject) technique, though it's usually not recommended:

function onReject(err) {
  console.log(err);
};
doSomethingAsync(param).then(onResolve, onReject);
function onResolve() {
  // Continue on with the rest of the code
}

const doSomethingAsync = () => new Promise((resolve, reject) => Math.random() < 0.5 ? resolve() : reject('bad'));

function onReject(err) {
  console.log(err);
};
doSomethingAsync().then(onResolve, onReject);
function onResolve() {
  console.log('continuing');
}

This will have onReject only handle errors thrown by doSomethingAsync(param). If your onResolve can throw inside its body as well, then you'll have to chain another .catch onto it (which will start to look a bit messy - it's usually nicer to catch errors in just one place)

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • @JuanMendes same goes for you :P Unless you were referring to the downvote on my question...since I don't know whether you or CertainPerformance downvoted me (but it seems as though it was you...no offence). – Kenny83 Jan 09 '20 at 02:33
  • Wasn't me, but best to check out https://meta.stackoverflow.com/q/388686 – CertainPerformance Jan 09 '20 at 02:34
  • @kenny83 you are mistaken buddy. I actually upvoted your question even though it's kind of unclear, but does show a problem you are trying to solve. I tried to guide you in the right direction with the comments. Please don't assume, you know what that does – Ruan Mendes Jan 09 '20 at 02:36
  • @kenny83 if you really want to check, check their rep history. That can at least prove if someone did not downvote you, look for a -1. :p. https://stackoverflow.com/users/227299/juan-mendes?tab=reputation – Ruan Mendes Jan 09 '20 at 02:43
  • 1
    Downvote actions do not show up in other users' rep history (you can only see it for your own's) – CertainPerformance Jan 09 '20 at 02:44
  • @JuanMendes LOL fair enough mate, thanks for trying to help and for the upvote, and apologies for any offence taken; it certainly wasn't intended :-) – Kenny83 Jan 09 '20 at 02:44
  • As CertainPerformance's link explains, it is much more pertinent to ask "*why* was my question/answer/comment downvoted", rather than "who is responsible for this abomination?!" LOL :P So if the downvoter sees this, please explain how I could better help myself and the community :-) – Kenny83 Jan 09 '20 at 02:47