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.
Related
I have an internal API that I would like to post data. Depends on some cases, I am seeing errors. So what I would like to do is to call it again if there is an error occurred.
What I did was to create a counter to pass it to the function and call the function recursively as below. This gives me the error as below:
UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
Here is how I call the function:
....
private RETRY_API = 1;
....
try {
await this.callAPI(request, this.RETRY_API);
} catch (error) {
console.log('error', error);
}
This program never comes to the catch block above.
And here is my actual function that I call the API:
private async callAPI(request, retry) {
return new Promise((resolve, reject) => {
someService.postApiRequest('api/url', request, async(err: any, httpCode: number, data) => {
if (this.RETRY_API == 2) {
return reject(err);
} else if (err) {
this.callAPI(request, retry);
this.RETRY_API++;
} else if ( httpCode !== 200 ) {
this.RETRY_API = 2;
// some stuff
} else {
this.RETRY_API = 2;
// some stuff
return resolve(data);
}
});
})
}
Not sure what I am missing. If there is a better way to call the API twice if an error occurred, that would be great if you let me know.
Let's organize a little differently. First, a promise-wrapper for the api...
private async callAPI(request) {
return new Promise((resolve, reject) => {
someService.postApiRequest('api/url', request,(err: any, httpCode: number, data) => {
err ? reject(err) : resolve(data);
});
});
}
A utility function to use setTimeout with a promise...
async function delay(t) {
return new Promise(resolve => setTimeout(resolve, t));
}
Now, a function that calls and retries with delay...
private async callAPIWithRetry(request, retryCount=2, retryDelay=2000) {
try {
return await callAPI(request);
} catch (error) {
if (retryCount <= 0) throw err;
await delay(retryDelay);
return callAPIWithRetry(request, retryCount-1, retryDelay);
}
}
If you can't force a failure on the api to test the error path some other way, you can at least try this...
private async callAPIWithRetry(request, retryCount=2, retryDelay=2000) {
try {
// I hate to do this, but the only way I can test the error path is to change the code here to throw an error
// return await callAPI(request);
await delay(500);
throw("mock error");
} catch (error) {
if (retryCount <= 0) throw err;
await delay(retryDelay);
return callAPIWithRetry(request, retryCount-1, retryDelay);
}
}
It looks like you need to add return await to the beginning of the line this.callAPI(request, retry); in callAPI function.
Similarly there are some condition blocks that doesn't resolve or reject the promise. While it might work okay, it's considered bad practice. You want to either resolve or reject a promise.
I've accomplished calling an API a second time when I received an error by using axios' interceptors functions.
Here is a code snippet you can review:
axios.interceptors.response.use(
// function called on a successful response 2xx
function (response) {
return response;
},
// function called on an error response ( not 2xx )
async function (error) {
const request = error.config as AxiosRequestConfig;
// request is original API call
// change something about the call and try again
// request.headers['Authorization'] = `Bearer DIFFERENT_TOKEN`;
// return axios(request)
// or Call a different API
// const new_data = await axios.get(...).then(...)
// return new_data
// all else fails return the original error
return Promise.reject(error)
}
);
Try replacing
if (this.RETRY_API == 2)
with
if (this.RETRY_API > 1)
This is working !
function rejectedPromise() {
return Promise.reject("ERROR");
}
async function someFunction() {
try {
rejectedPromise()
.catch((err) => console.log(err)); // IT WORKS ! THE ERROR IS BEING LOGGED HERE
}
catch(err) {
console.log(err);
}
}
someFunction();
This is not.
function rejectedPromise() {
return Promise.reject("ERROR");
}
async function someFunction() {
try {
rejectedPromise()
.catch((err) => { throw err; }); // CAN'T I THROW HERE ?
}
catch(err) {
console.log(err); // DOES NOT WORK. THE ERROR SHOULD BE LOGGED HERE
}
}
someFunction();
QUESTION
Why?
Note: my goal is to throw the error from the catch method of the rejected Promise and throw it so it's handled by an outer catch block. Don't know if it matters, but this is happening inside an async function (it doesn't work if I remove the async keyword from the example anyways).
EXTRA:
In Node, I'm getting this (even though there's a catch block):
UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with
.catch(). (rejection id: 1)
As pointed out in the comments, there is no need for using .catch() if you are inside an async function. I don't know why you want nested try...catch, but it looks like this would achieve what you want:
function rejectedPromise() {
return Promise.reject("ERROR");
}
async function someFunction() {
try {
try {
await rejectedPromise()
}
catch(err) {
throw err;
}
}
catch(err) {
console.log(err);
}
}
someFunction();
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
I have a service that analyses websites, compresses their sources like CSS Documents, Images etc. I have 2 functions, one is Socket.IO socket.on() method with async callback function. Another is main function for service.
socket.on('run', async options => {
debug(`${options.target} Adresine Bir Kullanıcı İstek Yaptı!`);
let user = null;
console.log(options);
if(options.token) {
user = await User.findById(jwt.verify(options.token, config.get('jwtPrivateKey'))._id);
options.userId = user._id.toString();
} else if(options.visitor) {
user = await Visitor.findById(options.visitor._id);
if(user.report) {
return socket.emit('error', new Error('You have exceeded your report limit'));
} else {
options.userId = user._id.toString();
}
}
if(options.userId) {
let userType = await UserType.find({ name: user.type });
if(userType.length > 0 && ((user.type == 'Visitor' && user.report == undefined) || (user.reports.length < userType[0].rights.reportsLimit.limit || userType[0].rights.reportsLimit.unlimited))) {
options.rights = userType[0].rights;
let { error, data } = await wrapper(runService(options.target, options, socket));
if(error) {
console.log('Here', error);
return socket.emit('error', error);
}
.
.
.
}
.
.
.
}
});
In the above function,
let { error, data } = await wrapper(runService(options.target, options, socket));
if(error) {
console.log('Here', error);
return socket.emit('error', error);
}
This part is important, because I call my main async service function runService with my async function wrapper function that is named wrapper. The wrapper function is this;
const wrapper = promise => (
promise
.then(data => ({ data, error: null }))
.catch(error => ({ error, data: null }))
);
In my main async service function, I only throw an error;
async function runService(target, options, socket) {
throw new Error('any error');
}
But the expected output is much different from actual output. Here is the output of this code;
Here Error: any error
at startService (C:\Projeler\OpDetect\Background-Service\lib\app.js:404:11)
at Socket.socket.on (C:\Projeler\OpDetect\Background-Service\app.js:73:57)
at process._tickCallback (internal/process/next_tick.js:68:7)
(node:16600) UnhandledPromiseRejectionWarning: Error: any error
at startService (C:\Projeler\OpDetect\Background-Service\lib\app.js:404:11)
at Socket.socket.on (C:\Projeler\OpDetect\Background-Service\app.js:73:57)
at process._tickCallback (internal/process/next_tick.js:68:7)
(node:16600) UnhandledPromiseRejectionWarning: Unhandled promise rejection.
This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:16600) [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.
My expectation about the output is, like this;
Here Error: any error
at startService (C:\Projeler\OpDetect\Background-Service\lib\app.js:404:11)
at Socket.socket.on (C:\Projeler\OpDetect\Background-Service\app.js:73:57)
at process._tickCallback (internal/process/next_tick.js:68:7)
Because I already handled the promise rejection with my wrapper function and catched the rejection, Why is 2 more UnhandledPromiseRejectionWarning errors on rejection?
Also, the line,
return socket.emit('error', error);
is not calling for no reason. It should have been called when the if statement truthy. Why is not this socket.emit function called?
As best practice use try {} catch(){} with async/await.
For ex.
userUtils.signUp = async (userName) => {
try {
const callFunction = await userUtils.checkExistancy(userName);
if (!callFunction.isExist) {
...
} else {
...
}
} catch (err) {
console.log(err);
throw err;
}
};
in your case it will be like
socket.on('run', async options => {
try {
user = await User.findById(jwt.verify(options.token, config.get('jwtPrivateKey'))._id);
options.userId = user._id.toString();
return true;
} catch (err) {
throw err;
}});
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.