Catching Errors in JavaScript Promises with a First Level Try ... Catch

Catching Errors in JavaScript Promises with a First Level try ... catch

With the new async/await syntax you can achieve this. Please note that at the moment of writing this is not supported by all browsers, you probably need to transpile your code with babel (or something similar).

// Because of the "async" keyword here, calling getSomeValue()
// will return a promise.
async function getSomeValue() {
if (somethingIsNotOk) {
throw new Error('uh oh');
} else {
return 'Yay!';
}
}

async function() {
try {
// "await" will wait for the promise to resolve or reject
// if it rejects, an error will be thrown, which you can
// catch with a regular try/catch block
const someValue = await getSomeValue();
doSomethingWith(someValue);
} catch (error) {
console.error(error);
}
}

handle promise error first level inside try catch

If you want to run it concurrently with your loop and properly handle errors from both (which necessarily includes waiting for both), then Promise.all is your only choice:

async function test() {
try {
await Promise.all([
testPromise(10),
(async function() {
for (let i=0; i<10000; i++) {

}

}())
]);
} catch(err) {
console.log(err);
}
}

Why doesn't the try catch block catch the promise exception?

I can see two possible aspects to your question:

  1. The error being thrown in test is in the synchronous (not asynchronous) part of test. Why is it a promise rejection rather than a synchronous exception?

  2. The promise from test() is being rejected, why doesn't catch catch that rejection?

#1 - Why is it a rejection?

Because even if an async function throws during the synchronous portion of its work, that just rejects the promise it returns, it doesn't raise a synchronous error. It's just a design decision made during the design of async functions, and a smart one — if it were a synchronous error when throwing in the synchronous part of the function but a promise rejection after that, it would be chaotic and hard to understand. So it's simple: throwing in an async function always rejects its promise.

(This is consistent with how the promise constructor treats the executor callback you pass into it. When you do new Promise((resolve, reject) => /*...*/}), the promise constructor calls your function synchronously, but if you throw during the call, it uses what you throw to reject the promise rather than allowing it to continue as a synchronous exception.)

#2 - Why isn't rejection caught by the catch block?

Because you're not using await. Promise rejections are only exceptions when you are awaiting the promise. If you use the promise methods then/catch/finally to attach handlers, rejection is handled by calling the rejection handler, not by the exception mechanism.

So either use promise methods to attach fulfillment and rejection handlers:

test()
.then(result => {
// ...use `result` here...
})
.catch(error => {
// ...handle/report error here...
});

Or use await in an async function (or at the top level of a module if you have top-level await in your enviroment):

// In an `async` function (or the top level of a module in cutting-edge environments)
try {
const result = await test();
// ...use `result` here...
}
catch(err){
// ...handle/report error here...
}

Why a promise reject is not catched within a try catch block but produces an Uncaught error?

Technical reasons behind:

HostPromiseRejectionTracker is an implementation-defined abstract
operation that allows host environments to track promise rejections.

An implementation of HostPromiseRejectionTracker must complete
normally in all cases. The default implementation of
HostPromiseRejectionTracker is to unconditionally return an empty
normal completion.

https://www.ecma-international.org/ecma-262/10.0/index.html#sec-host-promise-rejection-tracker

Basically javascript engines can freely implement this spec. In the case of browsers you don't get the Error captured inside the try/catch because the error is not emitted where you think it should be. But instead it's tracked with this special event that throws the error in the console.
Also on nodejs, this event causes the process to exit if you have node set to exit on unhandled exceptions.

On the other side, if you instead use async/await, the rejection is treated like an 'error' in practical terms. Meaning that the newer async/await feature behaves in a different fashion showing that it is not only syntactic sugar for Promises.

https://tc39.es/ecma262/#sec-throwcompletion

In sum, if you use Promise.then you are forced to provide a reject argument or chain it with .catch to have the rejection captured or else it will reach the console and in case of nodejs to exit the process if configured to do so (I believe new nodejs does this by default).
But if you use the newer async/await syntax you not only have a concise code (which is secondary) but a better rejection handling because it can be properly nested in a try/catch and it will behave like an standard Error.

NodeJs Error handling promise error with in try catch block

Promises work asynchronously, making them execute outside of the scope of the catch block you have here. This is why they have their own version of catch -

promise
.then(function(data) {
//some business logic
return anotherPromise;
})
.then(function(data) {
//some other business logic
})
// this keeps chaining as long as needed
.catch(function(err) {
// You will get any thrown errors by any of the
// chained 'then's here.
// Deal with them here!
});

This is the recommended way.

try catch vs promise chaining mechanism for error handing which one offers more flexibility?

Firstly, try... catch is useful to catch mostly expected errors (when you need to return some 4xxx error with additional info or even 200 with a business logic error code or something like that) rather than some unexpected errors that should lead to 500 HTTP errors. That said you need a global handler for all non-catched/unexpected errors that will log these errors and send the 500 error to a client.

global error handler

  app.use(function (err, req, res, next) {
logger.error(err)

res.status(500).send('Unexpected error')
})

try/catch with an explicit transaction

const registerNewUser = async (req, res) => {
/*
code here for extracting info from req
*/
// here is some call to create a transaction in a DB
// it varies depending on a type of DB and a package you use to communicate with it
// we need to create a transaction BEFORE try/catch block
const transaction = await createTransaction();
try {
// we need to pass transaction to every method that modifies data in DB
// also depends on how to pass a transaction to underlying DB package methods
const users_data = await users.create_new_user({
email,
crypted_pwd,
salt,
first_name,
phone_code,
}, transaction);

const { id: user_id, first_name: user_first_name } = users_data;

// we need to pass transaction to every method that modifies data in DB
const customer_data = await customers.create_new_customer({
first_name,
last_name,
email,
}, transaction);

const {
tenant_id,
id: customer_id,
} = customer_data;

await clearCartFromRedis(merchant_canonical_name, req.token);

signUpWelcomeMailer(merchant_canonical_name, req.token, user_id);

// here we need to commit the transaction before we exit from this handler
await transaction.commit();

return res.status(200).json({
success: true,
access_token: req.token,
first_name: user_first_name,
});
} catch (error) {
// here we need to rollback the transaction before we exit from this handler
await transaction.rollback();
logger.log({ message: error.message, level: 'error' });
return res.status(500).send(error.message);
}
};

Secondly, in your case of several operations against some DB you clearly need to use a transaction mechanism to keep data consistent. Usually, it requires creating an explicit transaction and passing it to each query that should be executed as a one atomic operation that either executes correctly (then you need to commit a transaction) or fails (then you need to rollback a transaction). For that, you can use try...catch to be able to commit or rollback a transaction at the end of a batch of operations.

How do catch a reject from a function which contains a promise but does not return it

You can listen for the "unhandledrejection" event.

window.addEventListener("unhandledrejection", e => {
e.preventDefault(); // prevent error message
// handle...
// e.promise is the Promise that was rejected
// e.reason is the object passed to reject()
});

Even with Try{} Catch (e){} the script does not ignore the error - Uncaught (in promise) SyntaxError: Unexpected end of Json Imput

fetch performs asynchronous operations and try/catch can only catch errors that are originated when first running the code inside its corresponding block (your error is being thrown on your first .then() callback). You can add a try/catch wrapping response.json() and it would work as you expect.

Alternatively, you can safely reformat your code as follows, leveraging promises catch method:

function refresh_images() {
const selectedstages = document.getElementById("matches").selectedOptions[0].getAttribute("data-stages");
const selectedevents = document.getElementById("matches").selectedOptions[0].getAttribute("data-events");

fetch("testedropdown.json")
.then(response => response.json())
.then(data => {
console.log(data.Stages[selectedstages].Events[selectedevents].Eps);
})
.catch((err) => {
console.log(err);
// HANDLE ERRORS HERE.
});
}

More info here.

Try and catch around promise

You don't say exactly how A() fails. It could either throw or it could return an error result. I'll show a scheme for both. The key to a mix of sync and async is to always return a promise. This will give you a consistent interface for teh caller no matter how the function succeeds or fails.

If you are only worried about A() throwing an exception and it doesn't return an error code, then you can do this:

function someFunction() {
try {
var result = A();
return B(result);
} catch(err) {
return Promise.reject(err);
}
}

someFunction().then(function(result) {
// code here to process the final result
}).catch(C);

If you also have the case where A() can return an error code, then you can do this:

function someFunction() {
try {
var result = A();
// check for error value
if (result < 0) {
throw result;
}
return B(result);
} catch(err) {
return Promise.resolve(err);
}
}

Note that both of these patterns avoid creating an extra promise if it isn't needed. They only create the extra promise when returning an error that occurred synchronously.


The Bluebird promise library has a helper function for this particular circumstance called Promise.method. The utility of Promise.method() is that it automatically wraps your function in a try/catch handler and if there are any synchronous exceptions thrown, it automatically turns them into returning a rejected promise. You could use it like this:

var someFunction = Promise.method(function() {
var result = A();
// check for error condition
if (result < 0) {
throw result;
}
return B(result);
});

someFunction().then(function(result) {
// code here to process the final result
}).catch(C);


Related Topics



Leave a reply



Submit