Search code examples
javascriptpromisedexie

Using a Promise inside a Dexie iteration


I am not really a Promise Ninja and I understand that I'm doing something wrong. However I cannot find some particular/simular problem to what I am having.

The problem: I use the Dexie.js wrapper for IndexedDB which is asynchronous. I have a global database which leads to some other dexie databases.

function handleDatabases() {
    var result = [];

    db.jobs.orderBy('title').filter(function(job) {
        return job.someBooleanCondition;
    }).each(function(job, cursor) {
        let jobDetails = new Dexie(job.correspondingDB);
        jobDetails.version(1).stores({
            details: 'key,value1,value2'
        }); 
        jobDetails.details.get(someKey).then(function(detail) {
            result.push({job: job, detail: detail});
        })
    }).catch(function(error) {
        console.log(error);
    });
    handleResult(result);
}

I have rewritten it for SO with a maybe strange form but the end goal is that i can use the array result to handle some update. However since it is asynchronous it is always empty until you inspect it in console where it is never empty. How can I rewrite this to be synchronous?


Solution

  • You cannot expect to return the result when that result only becomes available asynchronously.

    So you must stick with promises all the way (returning them each time), and let your function also return a promise. The caller must use then (or await if supported) to allow (asynchronous) access to the result.

    Instead of pushing the {job: job, detail: detail} to a results variable, return it. It will become the promised value for jobDetails.details.get(..).then(..). If you return also that, you'll have an array of promises, which can then be resolved with Promise.all

    Avoid to create new Promises as that usually leads to the promise constructor antipattern.

    Also avoid using a variable (like results) that is used in several callbacks without being passed as argument. Instead try to construct and return that array as a promised value, so it can be used in the next then callback.

    Here is the suggested (untested) code:

    function handleDatabases() {
        db.jobs
        .orderBy('title')
        .filter(job => job.someBooleanCondition)
        .toArray(jobs =>
            jobs.map(job => {
                let jobDetails = new Dexie(job.correspondingDB);
                jobDetails.version(1).stores({
                    details: 'key,value1,value2'
                });
                return jobDetails.details.get(someKey)
                       .then(detail => ({job: job, detail: detail}))
            }) // is returned
        )
        .then(result => Promise.all(result))
        .then(handleResult)
        .catch(error => console.log(error));
    }