简体   繁体   中英

JavaScript chaining promises from LocalForage in for loop

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);
                    }

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM