Neil W
Neil W

Reputation: 9257

JavaScript Promise : Catching errors within chained functions

I have a function that is used to add a record to the IndexDb database:

async function addAsync(storeName, object) {
    return new Promise((res, rej) => {
        // openDatabaseAsync() is another reusable method to open the db.  That works fine.
        openDatabaseAsync().then(db => {
            var store = openObjectStore(db, storeName, 'readwrite');
            var addResult = store.add(JSON.parse(object));
            addResult.onsuccess = res;
            addResult.onerror = (e) => {
                console.log("addResult Error");
                throw e;
            };
        }).catch(e => {
            // Error from "throw e;" above NOT GETTING CAUGHT HERE!
            console.error("addAsync ERROR > ", e, storeName, object);
            rej(e);
        });
    })
}

If I try to add a duplicate key, then I expect:

addResult.onerror = (e) => {
    console.log("addResult Error");
    throw e;
}

to capture that. It does.

But then, I also expect my

.catch(e => {
    // Error from "throw e;" above NOT GETTING CAUGHT HERE!
    console.error("addAsync ERROR > ", e, storeName, object);
    rej(e);
})

to catch that error. But instead I get an "uncaught" log.

Console output:

addResult Error
Uncaught Event {isTrusted: true, type: "error", target: IDBRequest, currentTarget: IDBRequest, eventPhase: 2, …}

Does that final .catch only handle exceptions from the openDatabaseAsync call? I would have thought now as it is chained to the .then.

In summary, here's what I would expect from the above code:

However, I would have thought that I should get the log from the line:

console.error("addAsync ERROR > ", e, storeName, object);

before the reject is sent back to the caller of addAsync(), which may be unhandled at that point.

Upvotes: 1

Views: 365

Answers (1)

Tomalak
Tomalak

Reputation: 338416

Your approach would benefit form a larger overhaul.

  • Generally, don't write a function as async when it's not also using await.
  • Don't use new Promise() for an operation that returns a promise, such as openDatabaseAsync() does. Return that promise, or switch to async/await.
  • It would be useful to wrap IndexedDB operations so that they follow promise semantics.

On the example of IDBRequest:

function promisifyIDBRequest(idbr) {
    return new Promise( (resolve, reject) => {
        idbr.onsuccess = () => resolve(idbr.result);
        idbr.onerror = (e) => reject(e.target.error);
    });
}

Now you can do this:

async function addAsync(storeName, object) {
    const db = await openDatabaseAsync();
    const store = openObjectStore(db, storeName, 'readwrite');
    return promisifyIDBRequest(store.add(JSON.parse(object)));
}

Add a try/catch block if you want to handle errors inside of addAsync().

It's worth checking out existing solutions that wrap the entire IndexedDB interface with promise semantics, such as https://github.com/jakearchibald/idb.


FWIW, the promise-chain variant of the above function would look like this:

function addAsync(storeName, object) {
    return openDatabaseAsync().then( (db) => {
        const store = openObjectStore(db, storeName, 'readwrite');
        return promisifyIDBRequest(store.add(JSON.parse(object)));
    });
}

both variants return a promise for an IDBRequest result.

Upvotes: 3

Related Questions