user8555937
user8555937

Reputation: 2387

Javascript promises: UnhandledPromiseRejectionWarning

I'm new to Javascript Promises. I'm trying to perform a MySQL query in Promises based style I've been scratching my head for over half an hour but I still can't find the problem in this code. I'm using this article as a reference.

mysql file

const mysql = require('mysql')
const Config = require('./Config.js');

let connection = mysql.createPool({
    connectionLimit: Config.mysql.connectionLimit,
    host:   Config.mysql.host,
    user: Config.mysql.user,
    password: Config.mysql.password,
    database: Config.mysql.database,
    multipleStatements: true,
});

module.exports = {
    query: function(query, binds) {
        return new Promise((resolve, reject)=>{
            connection.query(query, binds, (error, ret)=>{
                if(error)
                    return reject(error);
                else
                    resolve(ret);
            })
        })
    },

    close: function() {
        return new Promise((resolve, reject)=>{
            connection.end((error)=>{
                if(error)
                    return reject(error);
                else
                    resolve();
            });
        });
    }
}

actual script

const Mysql = require('mysql file');

try {

Mysql.query("SELECT `password` \
            FROM `m_users` \
            WHERE `email` = ?", 
            ['[email protected]'])
    .then(ret=>{
        if(ret.email)
            return res.send({
                params: req.body,
                message: 'Email address has already been taken.',
            })
    })
    .catch(error=>{
        throw error;

    })

catch( e )
{
    console.log(e); // is never reached
}

throw error is never fired. Instead in my node console I receive the following error:

(node:8684) UnhandledPromiseRejectionWarning: Error: ER_NO_SUCH_TABLE: Table 'forge.m_users' doesn't exist

........backtrace here .......

(node:8684) 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)

I know the table does not exist. I'm interested in finding out why .catch does not handle the error. What am I doing wrong? :/

Upvotes: 0

Views: 742

Answers (2)

T.J. Crowder
T.J. Crowder

Reputation: 1074286

This is an anti-pattern (but you're not by far the first person to do it!):

.catch(error=>{
    throw error;

})

What that says is "Create a new promise" (because catch creates a promise) "and if the original promise rejects, reject it with the exact same rejection reason (error)." So it's basically a no-op, it doesn't do anything. It's the promise equivalent of:

try {
    // something
} catch (error) {
    throw error;
}

Since it doesn't do anything, consider what the code looks like without it — the code is breaking one of the fundamental rules of promises, which is "handle rejections or pass the chain to something else that will." :-)

If you want to handle the rejection, don't throw from catch:

Mysql.query("SELECT `password` \
        FROM `m_users` \
        WHERE `email` = ?", 
        ['[email protected]'])
.then(ret=>{
    if(ret.email) {
        return res.send({
            params: req.body,
            message: 'Email address has already been taken.',
        });
    }
})
.catch(error=>{
    // Use `error` here without `throw`ing -- report it, put it in a log, etc.
});

In an update to the question you show that you have all of your code using the promises in a try/catch:

try {
    Mysql.query(/*...*/)
    .then(/*...*/)
    .catch(error=>{
        throw error;
    });
}
catch( e )
{
    console.log(e); // is never reached
}

In a non-async function, try/catch can't catch promise rejections, because by the time the promise is rejected, execution has already left the try block. When using promises directly, you use the catch method instead. Or you use async functions (more below).


Side note: Node.js has had support for async functions for a while now, which tend to be a bit simpler to write. Within an async function, your code could be:

// Within an `async` function
try {
    const ret = await Mysql.query("SELECT `password` \
            FROM `m_users` \
            WHERE `email` = ?", 
            ['[email protected]']);
    if(ret.email) {
        return res.send({
            params: req.body,
            message: 'Email address has already been taken.',
        });
    }
} catch (error) {
    // Use `error` here without `throw`ing -- report it, put it in a log, etc.
}

Using async at the top level of a module is coming soon, too...


In a comment you've asked:

I'm building an MVC API and I run more promise-based procedures in the same controller method. I'd like to return a message once an exception has been encountered and stop continuing the execution of the next other promised-based procedures. I'm bound to using async/await method or is there another way?

You don't have to use async/await if you don't want to, no; they just make things simpler (in my view).

If you don't want to use async/await, in the fulfillment callback you're passing then, return the promise from the next operation. That makes the promise then fulfill or reject based on what the promise frm the next operation does — in promise jargon, it resolves the promise from then to the one from the next operation.

Code is worth 1024 words, so:

doSomethingAsync()
.then(value => {
    // ...use `value`, then
    return doSomethingElseAsync();
})
.then(() => {
    // ...
    return doYetAnotherAsyncThing();
})
.catch(error => {
    // Handle/report error here
});

If any of the promises is rejected, any subsequent fulfillment handlers are skipped, and the rejection handler at the end (added via catch) is triggered.

That's roughly the promise version of this synchronous code:

try {
    const value = doSomethingSync();
    // ...use `value`, then
    doSomethingElseSync();
    // ...
    doYetAnotherSyncThing();
} catch (error) {
    // Handle/report error here
}

Upvotes: 2

Yone
Yone

Reputation: 976

basically, the reason you're getting an unhandled rejection is mixing async and sync code - promises are asynchronous and try{ ... } catch() { ... } only work in the synchronous mode. calling an asynchronous function in a synchronous try-catch block does not wait for it to settle. try to wrap your promise with async/await or handle the rejection in .catch() of the promise itself.

Upvotes: 1

Related Questions