2

I wonder if we can get error and result of a Promise without a try/catch block inside of the async function.

(async function(){
    let { user, error } = await getUser();
    .
    .
    .
    { user, error } = await user.save();
})();

Is there any cleaner way of error handling of Promises without using try/catch block or .then().catch()?

M. Çağlar TUFAN
  • 626
  • 1
  • 7
  • 21
  • 2
    The error doesn't get resolved, it gets rejected. So no, either try catch or .catch. – jonrsharpe May 14 '21 at 21:52
  • 1
    @jonrsharpe Okay, thanks for your answer. Let me add another question based on your comment, please. Consider that I have several Promises that are set to await, all of them doing different tasks. If I put them all inside a try/catch block how can I handle the error? I mean how do I know which one is throwing error? If I use .catch() on all of them, how do I callback return the error to the main async function? Do I need to use a callback? – M. Çağlar TUFAN May 14 '21 at 21:56
  • 3
    If you want to know which one errored, you _don't_ put them all in the same try block. – jonrsharpe May 14 '21 at 21:58
  • 1
    You have [`Promise.all`, `Promise.allSettled`, `Promise.any`, and `Promise.race`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) to aggregate multiple pending promises in a clean way. – Patrick Roberts May 14 '21 at 21:59
  • @jonrsharpe That was what I could think of a solution. But that sounds like callback hell with a disguise. What would you suggest me to do in such cases? – M. Çağlar TUFAN May 14 '21 at 21:59
  • 3
    You don't actually want the error and result of the promise, [it's not cleaner](http://callbackhell.com/). – Patrick Roberts May 14 '21 at 22:00
  • @PatrickRoberts I dont have much practice using them, but they seem helpful. Which Promise static method should I use for the example I have shown in question? And your second comment makes sense. Thanks. – M. Çağlar TUFAN May 14 '21 at 22:02
  • The Maybe monad is great for error handling. – bel3atar May 14 '21 at 22:04
  • @bel3atar can you provide a link please? I googled 'the maybe monad' but they seem very technical and deep ideas. – M. Çağlar TUFAN May 14 '21 at 22:08
  • @M.ÇağlarTUFAN there's a JS library called folktale, it has an implementation of Maybe. Basically maybe represents the existence or absence of value. You can chain many computation that might fail without try/catch or if/else. Very neat. It can be very intimidating but I'm sure you'll find examples online. https://folktale.origamitower.com/api/v2.3.0/en/folktale.maybe.html – bel3atar May 14 '21 at 22:12
  • 1
    Could you please add the code that would actually *handle* the `error` to your question? – Bergi May 14 '21 at 23:08
  • 1
    You might want to take a look at [Correct error handling syntax using `async`/`await`](https://stackoverflow.com/q/44663864/1048572) – Bergi May 14 '21 at 23:09

1 Answers1

2

How is that cleaner than this?

async function updateUser() {
    const user = await getUser();
    .
    .
    .
    const result = await user.save();
    return result;
    // or
    // return user.save();
}

If it errors, the caller of updateUser() knows if it should handle it and what it should do. If the caller doesn't know what to, then the caller will return a promise as well (either by virtue of being async and awaiting the call to updateUser() or by returning the promise returned by updateUser()), and it doesn't need to perform error handling.

Eventually you'll reach a frame in your call stack that knows what to do with an error, in which case it performs error handling for any possible rejected promises awaited. If not, then you've probably reached a callback from a framework or the entry point of your program which will likely propagate the error as an unhandled promise rejection.

Before promises, there were callbacks, and it took a lot of discipline not to write everything like this:

function updateUser(callback) {
    getUser(function (error, user) {
        if (error) {
            return callback(error);
        }

        user.save(function (error, result) {
            if (error) {
                return callback(error);
            }

            callback(null, result);
            // or
            // callback(error, result);
        });
        // or
        // user.save(callback);
    });
}

Notice that even callbacks which don't know what to do with the error have to explicitly perform error handling anyway, this is one of the reasons why promises suck so much less. One might point out that you can make it cleaner by extracting each callback into a named function to flatten out the recursion visually, but even then, each callback still has to perform error handling.

Then promises came along and the problem of explicit error handling at every frame eventually went away:

function updateUser() {
    return getUser().then(user => {
        return user.save();
    }).then(result => {
        return result;
    });
    // exposition only
}
Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
  • Thanks a lot. Your answer is very helpful my question. I have 3 questions about your examples. 1) In the first code block, you haven't used a try/catch for error handling. Was that intentional? I mean it would directly throw an UnhandledPromiseRejection I guess. 2) In your third code block, all we need to do is add a single .catch() to the chain to handle all the errors may occur, right? 3) I couldn't understand what you mean by 'exposition only' in your third code block. Can you explain it, please? – M. Çağlar TUFAN May 14 '21 at 22:48
  • @M.ÇağlarTUFAN 1) yes it was intentional. the whole point of the answer is to show that `async` / `await` provides the least boilerplate _because_ you don't get the error as part of the API. The result and the error are kept in completely separate control flows. 2) Similar to the explanation regarding first code block, you shouldn't need to add a `.catch()` block, unless `updateUser()` knows what to do with the error. 3) It just means that the `.then(result => { return result; });` is completely redundant and just shows where `result` went. You can remove it and it will still work the same. – Patrick Roberts May 14 '21 at 22:56
  • Oh, I got it. Thanks for answers. I will do my best to practice these fundamentals. Have a good day! – M. Çağlar TUFAN May 14 '21 at 23:00