Node.JS - Can`t get async throws with try/catch blocks - javascript

When I create an async function in node and use await, I'm making the execution waits for a promise resolution (that can be a resolve or a rejection), what I do is put an await promise inside a try/catch block and throw an error in case of a promise rejection. The problem is, when I call this async function inside a try/catch block to catch the error in case of it, I get an UnhandledPromiseRejectionWarning. But the whole point of using await isn't waiting for the promise to resolve and return it's result? It seems like my async function is returning a promise.
Example - The code an UnhandledPromiseRejectionWarning:
let test = async () => {
let promise = new Promise((resolve, reject) => {
if(true) reject("reject!");
else resolve("resolve!");
});
try{
let result = await promise;
}
catch(error) {
console.log("promise error =", error);
throw error;
}
}
let main = () => {
try {
test();
}
catch(error){
console.log("error in main() =", error);
}
}
console.log("Starting test");
main();

async functions always return promises. In fact, they always return native promises (even if you returned a bluebird or a constant). The point of async/await is to reduce the version of .then callback hell. Your program will still have to have at least one .catch in the main function to handle any errors that get to the top.
It is really nice for sequential async calls, e.g.;
async function a() { /* do some network call, return a promise */ }
async function b(aResult) { /* do some network call, return a promise */ }
async function c() {
const firstRes = (await (a() /* promise */) /* not promise */);
const secondRes = await b(firstRes/* still not a promise*/);
}
You cannot await something without being inside a function. Usually this means your main function, or init or whatever you call it, is not async. This means it cannot call await and must use .catch to handle any errors or else they will be unhandled rejections. At some point in the node versions, these will start taking out your node process.
Think about async as returning a native promise - no matter what - and await as unwrapping a promise "synchronously".
note async functions return native promises, which do not resolve or reject synchronously:
Promise.resolve(2).then(r => console.log(r)); console.log(3); // 3 printed before 2
Promise.reject(new Error('2)).catch(e => console.log(e.message)); console.log(3); // 3 before 2
async functions return sync errors as rejected promises.
async function a() { throw new Error('test error'); }
// the following are true if a is defined this way too
async function a() { return Promise.reject(new Error('test error')); }
/* won't work */ try { a() } catch(e) { /* will not run */ }
/* will work */ try { await a() } catch (e) { /* will run */ }
/* will work */ a().catch(e => /* will run */)
/* won't _always_ work */ try { return a(); } catch(e) { /* will not usually run, depends on your promise unwrapping behavior */ }

Main must be an async function to catch async errors
// wont work
let main = () =>{
try{
test();
}catch(error){
console.log("error in main() =", error);
}
}
// will work
let main = async () =>{
try{
test();
}catch(error){
console.log("error in main() =", error);
}
}

Related

Why can't try/catch handle error throw in Promise constructor

This code get's unhandledRejection error and I don't know why.
If the Error is thrown in try/catch, shouldn't it be caught by Catch Expression?
async function main () {
try {
await run(throwError)
} catch (error) {
console.log('main catch error', error);
}
}
async function run (callback) {
return new Promise(async resolve => {
await throwError()
});
}
async function throwError () {
throw new Error('custom error')
}
process.on('unhandledRejection', (reason, promise) => {
console.log('unhandledRejection - reason', reason, promise);
})
main()
It's not caught because you're passing an async function into new Promise. An error inside an async function rejects the promise that the function returns. The Promise constructor doesn't do anything a promise returned by the function you pass it (the return value of that function is completely ignored), so the rejection goes unhandled. This is one of the promise anti-patterns: Don't provide a promise to something that won't handle it (like addEventListener on the web, or the Promise constructor, or forEach, ...).
Similarly, there's no reason to use new Promise in your async function at all. async functions already return promises. That's another anti-pattern, sometimes called the explicit promise construction anti-pattern. (But see below if you're wrapping an old-fashioned callback API.)
If you remove the unnecessary new Promise, it works as you expect (I also updated run to call callback rather than ignoring it and calling throwError directly):
async function main() {
try {
await run(throwError);
} catch (error) {
console.log("main catch error", error);
}
}
async function run(callback) {
return await callback();
}
async function throwError() {
throw new Error("custom error");
}
process.on("unhandledRejection", (reason, promise) => {
console.log("unhandledRejection - reason", reason, promise);
});
main();
About the return await callback(); in that example: Because whatever you return from an async function is used to settle the function's promise (fulfilling it if you return a non-thenable, resolving it to the thenable if you return one), and that return is at the top level of the function (not inside a try/catch or similar), you could just write return callback();. In fact, you could even remove async from run and do that. But keeping the await makes the async stack trace clearer on some JavaScript engines, more clearly indicates what you're doing, and keeps working correctly even if someone comes along later and puts a try/catch around that code.
In a comment, you said that you used new Promise because you were wrapping around a callback-based API. Here's how you'd do that (see also the answers here):
// Assuming `callback` is a Node.js-style callback API, not an
// `async` function as in the question:
function run(callback) {
return new Promise((resolve, reject) => {
callback((err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
but, you don't have to do that yourself if the callback-based API uses the standard Node.js convention as above, there's a promisify function in utils.
You don't want to throw an error in this case, you want to invoke reject:
return new Promise((resolve, reject) => {
reject('custom error')
});
If the error being thrown is out of your control, you can catch it inside the promise implementation and reject in that case.

Why does line after return reject() execute in below function?

For the code below why does "A" and "B" get printed before the error 'a'
var a = async()=>{
return new Promise(async (resolve,reject)=>{
var v = await ab().catch((err)=>{
console.log("error")
return reject(err)
})
console.log("A")
console.log("B")
})
}
function ab(){
return new Promise((resolve,reject)=>{
return reject("a")
})
}
async function e(){
try{
await a()
}catch(e){
console.log(e)
}
}
e()
The output for the above code is
error
A
B
a
Shouldn't it be "error" followed by 'a'
Why are A and B getting printed?
Because there's no reason it shouldn't. The return is in a callback function, so it only returns from that callback function. It does reject the outer promise, but doing that doesn't prevent the remainder of the code in your promise executor (the function you pass new Promise) from continuing.
There are several problems with the code shown, though.
If you already have a promise, you don't need to create a new one. In a, for instance, you have the promise from ab, so there's no reason for new Promise. This is often called the explicit Promise creation antipattern, this question's answers go into detail.
Using await in a promise executor function generally doesn't make much sense and probably means the executor is doing too much. The purpose of a promise executor is to start an asynchronous process, not to wait for one.
Using .catch/.then/.finally inside an async function is generally not best practice; just use await and try/catch.
Your a function should probably be just:
const a = async () => {
try {
await ab();
// Or you might put the `console.log`s here, there are
// reasons for each, but it doesn't matter given we're
// rethrowing the error
} catch (err) {
console.log("error")
throw err;
}
console.log("A")
console.log("B")
};
That will reject the promise a returns when ab's promise rejects, using the same rejection reason, and terminate a's logic rather than allowing it to continue; if ab's promise doesn't reject, it will run the two console.log statements.
Live Example:
const a = async () => {
try {
await ab();
// Or you might put the `console.log`s here, there are
// reasons for each, but it doesn't matter given we're
// rethrowing the error
} catch (err) {
console.log("error")
throw err;
}
console.log("A")
console.log("B")
};
function ab() {
return new Promise((resolve, reject) => {
return reject("a");
});
}
async function e() {
try {
await a();
} catch (e) {
console.log(e);
}
}
e();

"await is only valid in async function" in for loop

I'm being told that "await is only valid in async function", even though it is in a async function. Here is my code:
async function uploadMultipleFiles (storageFilePaths,packFilePaths,packRoot) {
return new Promise((resolve,reject) => {
try {
for (i in storageFilePaths) {
await uploadFile(storageFilePaths[i],packFilePaths[i],packRoot) // error throws on this line
}
resolve("files uploaded")
} catch {
console.log(err)
reject("fail")
}
})
}
Why is this happening when I made it an async function? Is it because I am using a for loop? If so, how can I get the expected outcome without this error?
The function you define starting on line 1 is async.
The arrow function you define on line 2 and pass to the Promise constructor is not async.
You are also using the multiple promise anti-pattern. Get rid of the Promise constructor entirely. Just return the value when you have it. That's one of the main benefits of the async keyword.
async function uploadMultipleFiles(storageFilePaths, packFilePaths, packRoot) {
try {
for (i in storageFilePaths) {
await uploadFile(storageFilePaths[i], packFilePaths[i], packRoot) // error throws on this line
}
return "files uploaded";
} catch {
console.log(err);
throw "fail";
}
}
You can only use await inside of an async function, the error refers to the callback your passing to your new Promise (since you are entering a new function scope there).
async function uploadMultipleFiles (storageFilePaths,packFilePaths,packRoot) {
return new Promise((resolve,reject) => { // <========= this arrow function is not async
try { // so you cant use await inside
for (i in storageFilePaths) {
await uploadFile(storageFilePaths[i],packFilePaths[i],packRoot) // error throws on this line
}
resolve("files uploaded")
} catch {
console.log(err)
reject("fail")
}
})
}
The part where you try to construct a new Promise is actually redundant since an async function will resolve to a Promise anyways (read more here). So you could write your code as follows:
async function uploadMultipleFiles (storageFilePaths,packFilePaths,packRoot) {
try {
for (i in storageFilePaths) {
await uploadFile(storageFilePaths[i],packFilePaths[i],packRoot) // error throws on this line
}
return "files uploaded"
} catch {
console.log(err)
throw new Error("fail");
}
}
The Promise callback isn't async
async function uploadMultipleFiles (storageFilePaths,packFilePaths,packRoot) {
return new Promise(async (resolve,reject) => {
try {
for (i in storageFilePaths) {
await uploadFile(storageFilePaths[i],packFilePaths[i],packRoot) // error throws on this line
}
resolve("files uploaded")
} catch {
console.log(err)
reject("fail")
}
})
}

Why am I unable to catch discord.js promise rejections in event callbacks?

So I'm making a discord bot. For simplicity's sake, here is a very small portion that illustrates my problem:
const Discord = require('discord.js');
const client = new Discord.Client();
client.on('ready', async () => {
throw new Error('Omg');
});
async function start() {
try {
await client.login(process.env.DISCORD_BOT_TOKEN);
} catch (err) {
console.error('Caught the promise rejections');
}
}
start();
When I run this code, I expect the output to be Caught the promise rejections and the process should subsequently exit. However this is not the case. Instead I get a PromiseRejectionWarning and the process does not exit (I have to press Ctrl-C to do so). I first thought that maybe errors in callbacks don't get propagated to code that calls them, so I made another pure JS example:
const client = {
on(event, callback) {
this.callback = callback;
},
async login(token) {
while (true) {
// I assume the login method calls the callback in D.js
// (where else could it be called?)
await this.callback();
await sleep(5000);
}
},
};
client.on('ready', async () => {
throw new Error('Omg');
});
async function start() {
try {
await client.login(process.env.DISCORD_BOT_TOKEN);
} catch (err) {
console.error('Caught the promise rejections');
}
}
start();
However in this case, the output is exactly as expected; I see the line from the catch and the process immediately exits. Without the catch I get the unhandled promise rejection errors and an unfinished process.
So my question: Why am I unable to catch promise rejections in my event callbacks (like on('ready'))?
The reason is, because your second code, is not how discord event emitter works, nor Node.js built in EventEmiter does.
The callback function for the ready event is not executed with an await, and it doesn't have a .catch handler attached to it, that's why you get an UnhandledPromiseRejectionWarning.
When using async in an EventEmitter callback, you should handle the error, if you don't you'll get the warning, because no other code is handling it.
client.on('ready', async () => {
try {
throw new Error('Omg');
} catch(e) {
}
});
In your specific case, it seems that you want to trigger an error if some condition is met on 'ready'. So what you should do instead, is wrap that listener in a Promise.
function discordReady(client) {
return new Promise((resolve, reject) => {
client.once('ready', async () => {
reject(new Error('Omg'));
// resolve..
});
})
}
async function start() {
try {
await Promise.all([
discordReady(client),
client.login(process.env.DISCORD_BOT_TOKEN),
]);
} catch (err) {
console.error('Caught the promise rejections');
}
}
That will get you the expected behaviour

Using Try/Catch with Promise.all

I have recently been reading about async/await and using try and catch to handle promise rejections, and have been applying it to some of my old code.
I have the following:
async function() {
try {
await Promise.all([some functions]);
doIfNoError();
} catch (error) {
console.log(error);
}
The functions I am passing to Promise.all follow the form:
async function() {
some code
if (some condition) {
return true
} else {
throw false
}
}
I intend that if any of the functions passed into Promise.all reject, the rejection is displayed. If none of the functions reject, then doIfNoError should fire. However, doIfNoError sometimes fires when it shouldn't, and I am given the error "Unhandled Promise Rejection".
Actually, try/catch does work well with Promise.all().
Here is a snippet to prove it:
async function p1() {
return 1;
}
async function boom() {
throw new Error('boom');
}
// expected output: 'boom', ["value before Promise.all"]
async function asyncCall() {
let all = ['value before Promise.all'];
try {
all = await Promise.all([p1(), boom()]);
} catch(e) {
console.log(e.message);
}
console.log(JSON.stringify(all));
}
asyncCall();
Try using promises to their full potential, which includes a catch block for rejected promises. Note that if doIfNoError also throws an error, it will be caught with catch.
async function() {
await Promise.all([some promises])
.then(doIfNoError) // Promise.all resolved
.catch(console.log) // Promise.all has at least one rejection
}
promiseAll = async (promises) => {
await Promise.all(promises)
.then(doIfNoError) // Promise.all resolved
.catch(console.log) // Promise.all has at least one rejection
}
doIfNoError = () => console.log('No errors');
promiseAll([Promise.resolve(), 1, true, () => (false)]);
promiseAll([Promise.resolve(), 1, true, Promise.reject('rejected: because reasons'), Promise.resolve()]);
It's often considered bad practice to mix async/await with Promise chains. Using a try/catch should work just fine.
promiseAll = async (promises) => {
try {
await Promise.all(promises);
doIfNoError(); // Promise.all resolved
} catch(error) {
console.log(error); // Promise.all has at least one rejection
}
}
doIfNoError = () => console.log('No errors');
promiseAll([Promise.resolve(), 1, true, () => (false)]);
promiseAll([Promise.resolve(), 1, true, Promise.reject('rejected: because reasons'), Promise.resolve()]);
1) throw false – this does not make sense, you should not throw a boolean but an instance of Error instead.
2) catch is only triggered when an error is thrown or the promise is "rejected" (note: reject and throw have the same effect with async-await). In contrast: "Resolving" a promise with a boolean value of false is not interpreted by async-await as an exception. So make sure you throw an error or reject the promise if you want the catch-block to kick in. if you reject, pass the exception as argument to reject, e.g. (Promise.reject(new Error("Something went wrong")).
Apart from what I mentioned above, your code looks fine.

Categories

Resources