I am struggling to get my head wrapped around what the proper pattern is for handling errors within nested await/async routines, yet keeping the code clean and simple. (despite reading countless articles and blogs)
I have a set of functions that are (fundamentally) similar to the following:
async validate(params) {
    const recCount = await this._getCount(db, params);
    if( recCount > 0 )
        return "Record already exists";
}
_getCount is a wrapper that creates the sql
async _getCount(conn, regdata) {
    const sql = "SELECT count(*) AS 'count' FROM myTable WHERE product = ? and category = ?";
    let rows = await this._execSQL(conn, sql, [ regdata.product, regdata.category ]);
    return rows[0].count;
}
and the actual query is executed as follows:
async _execSQL(conn, sql, data) {
    const [ rows ] = await conn.query(sql, data);
    return rows;
}
The method conn.query (from the mysql2/promise library) will reject the promise if the query fails.
So, my question becomes what is the proper pattern for handling the exceptions?
In a synchronous world, I could nothing to the _execSQL nor the _getCount and just catch the exception in validate; just naturally letting the exception bubble up.
However, in the async world how can I do the equivalent without getting the 'Unhandled Promise' exception?
Am I stuck with having to catch the error at every single async routine all the way through the levels?
Or is there a better way without using something like process.on('unhandledRejection',...) which feels like I am circumventing the problem?
EDIT: Added example and stack trace
Ok, so I have actually added this code to my application and put the try/catch in the validate function.   Verbatim code is:
async validate(db, params) {
    let recCount;
    try {
        recCount = await this._getCount(db, params);
    } catch (err) {
        console.log('Caught error', err);
    }
    if (recCount > 0) return 'Record already exists';
}
async _getCount(conn, regdata) {
    const sql = "SELECT count(*) AS 'count' FROM myTable WHERE product = ? and category = ?";
    let rows = await this._execSQL(conn, sql, [ regdata.product, regdata.category ]);
    return rows[0].count;
}
async _execSQL(conn, sql, data) {
    const [ rows ] = await conn.query(sql, data);
    return rows;
}
I have a event handler for unhandledRejection, which reports out the event along with the inner exception along with the stack trace. This is what it dumps out:
Stack Trace:
AppError: Unhandled promise rejection.   Plugin may not be properly handling error.
    at process.on (D:\Development\website\service\server.js:73:5)
    at emitTwo (events.js:126:13)
    at process.emit (events.js:214:7)
    at emitPendingUnhandledRejections (internal/process/promises.js:108:22)
    at process._tickCallback (internal/process/next_tick.js:189:7)
Inner Error:
{   "message": "connect ECONNREFUSED 127.0.0.1:3306",   "code": "ECONNREFUSED",   "errno": "ECONNREFUSED" }
Error: connect ECONNREFUSED 127.0.0.1:3306
    at PromisePool.query (D:\Development\website\webhooks\node_modules\mysql2\promise.js:323:22)
    at Registration._execSQL (D:\Development\website\webhooks\plugins\registration.js:108:31)
    at Registration._logRequest (D:\Development\website\webhooks\plugins\registration.js:179:14)
    at Registration.register (D:\Development\website\webhooks\plugins\registration.js:52:8)
    at Router.exec (D:\Development\website\service\router.js:119:20)
    at IncomingMessage.request.on (D:\Development\website\service\server.js:292:47)
    at emitNone (events.js:106:13)
    at IncomingMessage.emit (events.js:208:7)
    at endReadableNT (_stream_readable.js:1064:12)
    at _combinedTickCallback (internal/process/next_tick.js:138:11)
 
    