0

I'm attempting to chain multiple getItem promises from the LocalForage library, the keys are read from an Array.

PROBLEM: I need the Resolve or Reject callback to trigger AFTER all the LocalForage promises are finished.

Both methods didn't have the right callstack. Any ideas?

CODE 1:

function load(keyHandlerPairs, correct, incorrect) {
    var result = {
                    resolved: 0,
                    rejects: 0,
                },
                p = new Promise(function (resolve, reject) {
                    //Retrieve objects from localstorage;
                    if ($.isArray(keyHandlerPairs)) {
                        for (var i = 0; i < keyHandlerPairs.length; i++) {
                            var kv = keyHandlerPairs[i];
                            lf.getItem(kv.key, kv.handler).then(
                                //Resolved
                                function () { console.log('resolved'); result.resolved++; }
                            );
                        }
                    } else {
                        var kv = keyHandlerPairs;
                        lf.getItem(kv.key, kv.handler);
                    }


                    if ((result.resolved + result.rejects) == keyHandlerPairs.length) {
                        console.log(result.resolved, result.rejects, keyHandlerPairs.length);
                        resolve(result);
                    } else {
                        console.log(result.resolved, result.rejects, keyHandlerPairs.length);
                        reject(result);
                    }
                }).then(correct, incorrect);
}

CODE alt:

if ($.isArray(keyHandlerPairs)) {
                        var promises = [];
                        for (var i = 0; i < keyHandlerPairs.length; i++) {
                            var kv = keyHandlerPairs[i];
                            promises.push(lf.getItem(kv.key, kv.handler));
                        }

                        Promise
                            .all(promises)
                            .then(function (value) { console.log(value); result.resolved++; })
                            .catch(function (error) { console.log(error); result.rejects++; });

                    } else {
                        var kv = keyHandlerPairs;
                        lf.getItem(kv.key, kv.handler);
                    }
p.campbell
  • 98,673
  • 67
  • 256
  • 322
Jeroen Vorsselman
  • 803
  • 1
  • 9
  • 19

1 Answers1

1

I need the Resolve or Reject callback to trigger AFTER all the LocalForage promises are finished.

Yes. lf.getItem is asynchronous, but you are testing for (result.resolved + result.rejects) == keyHandlerPairs.length right after having fired all calls. Both .resolved and .rejects will still be 0 (notice that you never increment the number of rejects anyway).

First, you should not use the Promise constructor other than to construct a promise for a single asynchronous API that does not yet support promises. There should no logic go into it, just a plain call. Let's assume you wanted to use Promise.all to wait for all your concurrently running promises and get the combined results from them as a promise for an array. Your code should look like this:

if (!$.isArray(keyHandlerPairs))
    keyHandlerPairs = [keyHandlerPairs];
var promises = keyHandlerPairs.map(function(kv) {
    return lf.getItem(kv.key, kv.handler);
});
return Promise.all(promises);

OK, now assume you actually don't care about the results and whether all promises succeeded, but only about the numbers of fulfilled vs rejected promises. OK, there is no native combinator function for this, so we need to write our own (and use the Promise constructor). However, this functionality should be generic and have nothing to do with key-value handler pairs.

Promise.countResolutions = function(promises) {
    var result = {fulfillments:0, rejections:0},
        len = 0;
    return new Promise(function(resolve, reject) {
        function addResult(type) {
            result[type]++;
            if (result.fulfillments+result.rejections == len)
                resolve(result);
            return true;
        }
        try {
            let i = 0;
            for (let promise of promises) {
                let called = false;
                i++;
                promise.then(function onfulfilled() {
                    if (!called) called = addResult("fulfillments");
                }, function onrejected() {
                    if (!called) called = addResult("rejections");
                });
            }
            len = i;
            if (result.fulfillments+result.rejections == len)
                resolve(result);
        } catch (e) {
            reject(e);
        }
    });
};

Yes, it isn't as trivial as it looks at first (OK, maybe above version is a bit too detailed). But once you have this, you can simply replace .all() in the first snippet by .countResolutions() and you get the expected result.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I've got a question about your code. Why do you use 'let' instead of 'var'? What's the difference between them? – Jeroen Vorsselman Oct 01 '14 at 14:51
  • So that the `let called` is local to the for-loop body, not local to the `function(resolve, reject) {` scope as a `var` would be (see http://stackoverflow.com/q/762011/1048572) – Bergi Oct 01 '14 at 14:53
  • Firefox v32 doesn't seem to support the use of 'let'. I get syntax errors when using that code. So i adjusted your method for counting promises (removed called variable, changed for loop, changed addResult) and i got it to work! FIDDLE: http://jsfiddle.net/t3v61te1/ – Jeroen Vorsselman Oct 02 '14 at 09:50
  • Version that also returns the value from a Promise when counting: http://jsfiddle.net/t3v61te1/1/ – Jeroen Vorsselman Oct 02 '14 at 09:59
  • Hm, since you're using native promises I didn't thought you'd refrain from using [`let`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let) and [`for of`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of) :-) You seem to need to put ` – Bergi Oct 02 '14 at 10:02