0

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?

Emptyless
  • 2,964
  • 3
  • 20
  • 30
  • 1
    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` to allow (asynchronous) access to the result. – trincot Apr 01 '17 at 22:47
  • Thanks that worked, i created a promise inside the .each() function and after that called .then() with a Dexie.Promise.all() which worked. – Emptyless Apr 01 '17 at 22:55
  • OK, you might want to check whether you can do without creating a (new) promise, because `get(...).then(...)` already returns a promise. So just return that one. – trincot Apr 01 '17 at 22:58
  • If I just use the `get(...).then(...)` then the promise is only getting `detail` while i need to bundle it with the `job`, creating a new `Dexie.Promise` with a resolve of `{job: job, detail: detail}` gives me all the data I need. – Emptyless Apr 01 '17 at 23:01
  • 1
    Sorry to insist, but if you return `{job: job, detail: detail}` inside the `then` callback instead of pushing it to *result*, it should work without a new promise, no? – trincot Apr 01 '17 at 23:09
  • Could you maybe add an example of how to accomplish that? I believe your way is cleaner but I have no clue how to change the code in order to accomplish it. – Emptyless Apr 01 '17 at 23:14
  • 1
    Could you share your latest version of the code in an addendum to your question? Then I know for sure I start with working code, before making the adjustments ;-) – trincot Apr 01 '17 at 23:21
  • I've shared what works so far, thanks for looking into it! – Emptyless Apr 01 '17 at 23:26

1 Answers1

4

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));
}
trincot
  • 317,000
  • 35
  • 244
  • 286