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.
Related
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.
I'm trying to loop through the list of files and eventually save the array in my local drive.
However, the problem is once my code confronts any error, it stops running and doesn't save anything. What I want to achieve is to keep my loop running even if there is an error.
I'm still not fully confident with using Promise,
I'm going to fetch information using below code.
function getSongs(id, number) {
return new Promise((res, rej) => {
geniusClient.getArtistSongs(id, { "page": `${number}`, "per_page": "50" }, (error, songs) => {
// console.log(error);
// console.log(JSON.parse(songs).response.songs);
if (error) {
res('error', 'id is: ', id);
} else {
let songsArray = JSON.parse(songs)
// console.log(songsArray.response.songs)
res(songsArray.response.songs);
}
})
})
}
and save the songs once I fetch all of them as below.
for (artist of resultArray) {
console.log(artist.id);
let songArray = await getSongs(artist.id, 1);
artist.songs.push(...songArray)
}
// for (artist of resultArray) {
// console.log(artist.id);
// let songArray = await getSongs(artist.id, 2);
// artist.songs.push(...songArray)
// }
roundnumber++;
console.log('round number is', roundnumber);
fs.writeFileSync('./songTest/test.json', JSON.stringify(resultArray))
Suggested approach ...
Make sure that getSongs() returns a rejected Promise for as many error cases as possible.
function getSongs(id, number) {
return new Promise((res, rej) => {
// a synchronous error here will result in Promise rejection.
geniusClient.getArtistSongs(id, { "page": `${number}`, "per_page": "50" }, (error, songs) => {
try {
if (error) {
throw error; // will be caught below and result in Promise rejection.
} else {
// an (unexpected) error thrown here will also be caught below and result in Promise rejection.
let songsArray = JSON.parse(songs);
res(songsArray.response.songs);
}
} catch(error) {
rej(error); // reject (expected/unexpected error)
}
});
});
}
In the caller code, add a try/catch structure to handle errors.
for (let artist of resultArray) {
// ^^^ don't forget to declare variables
try {
let songArray = await getSongs(artist.id, 1);
artist.songs.push(...songArray);
} catch(error) {
// catch any error arising from the try block,
// including any arising from Promise rejection in getSongs().
artist.songs.push({ 'error': error.message, 'id': artist.id }); // or whatever you want to represent an error
}
}
You could use Promise.allSettled. From the MDN docs
The Promise.allSettled() method returns a promise that resolves after all of the given promises have either fulfilled or rejected, with an array of objects that each describes the outcome of each promise.
Store the promises in an array, don't await them and pass that array to Promise.allSettled.
With this all your errors (if any) will be stored and returned to you in array at the end of the operation.
How can I catch exceptions from doAsyncThing without throwing an await in? Each run of doAsyncThing is idempotent so I just want all of the operations to happen "at the same time" & then wrap up the process. I realized even if I put a try/catch inside the map, it won't actually catch it there because it's not in same thread at that point.
const promises = entities.map(entity => {
return entity.doAsyncThing();
});
await Promise.all(promises);
Can I catch exceptions from async methods without await?
No, not with try/catch. At the time your map callbacks finish, your async tasks have not even completed yet. promises contains an array of unsettled promises (assuming doAsyncThing is actually doing async work and does not directly return a resolved or rejected promise).
Depending on what exactly you want to do, you have several options.
Use catch within map to handle each thing individually:
const promises = entities.map(entity => {
return entity.doAsyncThing().catch(error => /* do something */);
});
await Promise.all(promises);
Use catch on Promise.all:
const promises = entities.map(entity => {
return entity.doAsyncThing();
});
Promise.all(promises).catch(error => /* do something */);
Use try/catch:
const promises = entities.map(entity => {
return entity.doAsyncThing();
});
try {
await Promise.all(promises)
} catch (ex) {
// do something
};
Depends on what are you trying to achieve. Same as your code but without await would be
const promises = entities.map(entity => {
return entity.doAsyncThing();
});
Promise.all(promises).catch(e => {
// handle exception
});
But you need to understand that it will catch if ANY of your promises reject and resolve if ALL of your promises resolve.
If you want to handle every exception and get results of resolved promises you may need something like
const promises = entities.map(async entity => {
try {
return await entity.doAsyncThing();
} catch (e) {
// handle each exception
return null;
}
});
await Promise.all(promises);
UnhandledPromiseRejectionWarning on async await promise
I have this code:
function foo() {
return new Promise((resolve, reject) => {
db.foo.findOne({}, (err, docs) => {
if (err || !docs) return reject();
return resolve();
});
});
}
async function foobar() {
await foo() ? console.log("Have foo") : console.log("Not have foo");
}
foobar();
Which results with:
(node:14843) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): false
(node:14843) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Note: I know I can solve this issue like this:
foo().then(() => {}).catch(() => {});
But then we are "back" to callbacks async style.
How do we solve this issue?
Wrap your code in try-catch block.
async function foobar() {
try {
await foo() ? console.log("Have foo") : console.log("Not have foo");
}
catch(e) {
console.log('Catch an error: ', e)
}
}
then(() => {}).catch(() => {}) isn't needed because catch doesn't necessarily should go after then.
UnhandledPromiseRejectionWarning means that a promise weren't synchronously chained with catch, this resulted in unhandled rejection.
In async..await, errors should be caught with try..catch:
async function foobar() {
try {
await foo() ? console.log("Have foo") : console.log("Not have foo");
} catch (error) {
console.error(error);
}
}
The alternative is to handle errors at top level. If foobar is application entry point and isn't supposed to be chained anywhere else, it's:
foobar().catch(console.error);
The problem with foo is that it doesn't provide meaningful errors. It preferably should be:
if (err || !docs) return reject(err);
Also, most popular callback-based libraries have promise counterparts to avoid new Promise. It mongoist for mongojs.
Every solution here just silences the error, but you should probably handle the error instead.
How you handle it depends on the error and on what part of the application you're in. Here are some examples.
You're writing an app
If you're writing a node app and something throws, you might want to use process.exit(1) to quit the app and display the error to the user:
async function init() {
await doSomethingSerious();
}
init().catch(error => {
console.error(error);
process.exit(1)
});
You're writing a module
If the code expects an error, you can catch it and use it as a value instead:
module.exports = async function doesPageExist(page) {
try {
await fetchPage(page);
return true;
} catch (error) {
if (error.message === '404') {
return false;
}
// Unrecognized error, throw it again
throw error;
}
}
Notice that this example re-throws the error when it's not the expected one. This is fine. It's the final user’s responsibility to handle network errors:
const doesPageExist = require('my-wonderful-page-checker');
async function init() {
if (await doesPageExist('https://example.com/nope')) {
console.log('All good')
} else {
console.log('Page is missing 💔')
}
}
// Just like before
init().catch(error => {
console.error(error);
process.exit(1)
});
You're the user
If you're seeing this error when using a prepackaged application via command line, like webpack or babel, it might mean that the application had an error but it was not handled. This depends on the application or your input is not correct. Refer to the application’s manual.
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);
}
}