0

I have a variable amount of $.Deferred() objects that are getting pushed into an array for execution via $.when(). Using suggestions from elsewhere on Stack Overflow, I decided to go with the hand-coded $.when.all([$.Deferred(), ...]) method.

To be specific, the $.Deferred() objects contain a function that executes one indexedDB .put() per object. I would expect them to complete upon calling $.when.all(), and execute code within it's corresponding .then() function, yet I discovered via breakpoints that it immediately executes the .put() upon invoking it, and the execution of the javascript bypasses the .then() function all together and just continues on.

var putQueryArray = [];

_.each(favorites, function(fav) { // favorites being an array of updated indexedDB .get()s
  var objectStore = db.transaction(['store'], 'readwrite') // var db defined elsewhere
                        .objectStore('store');

  putQueryArray.push($.Deferred(function() {
    var update = objectStore.put(fav);  // This executes immediately, as verified in Chrome's Resources tab

    update.onsuccess = _.bind(function(e) {
      this.resolve(e);  // Even if I put a breakpoint here, it never triggers
    }, this);

    update.onerror = _.bind(function(e) {
      this.reject(e);
    }, this);
  }).promise());
});

// It appears this is ignored all together
$.when.all(putQueryArray).then(function(res) {  // I (think I) expect this line to execute the above array of $.Deferred()'s?
  //This function never executes
}, function(err) { ... });

I've tried everything from not wrapping the entire function in $.Deferred(function() { ... });, instead calling $.Deferred as a variable within the function and returning its promise, to excluding the .promise() part all together, both of which result in it actually executing the .then() function, but not actually executing the .put(). What am I doing wrong in the above code?

Community
  • 1
  • 1
Scott
  • 6,716
  • 9
  • 40
  • 46

1 Answers1

2

The answer where you discovered jQuery.when.all() defines that method before using it. In other words, jQuery.when.all() is a custom, not native, jQuery method.

It is designed to overcome the well-understood and well-tolerated issue with jQuery.when() that it accepts only discrete promises as arguments, not an array of promises.

The reason this is not a big issue is because javascript's native Function.prototype.apply allows any function to be called with an array of arguments in place of discrete arguments (and for a thisArg to be specified).

In fact, if you read through the answer where you discovered the custom method, then you will see that is exactly what it does for you.

In full, not using jQuery.when.all(), you would write :

$.when.apply(null, putQueryArray).then(function(res) {
    ...
}, function(err) {
    ...
});

Many people choose to write $.when.apply($, putQueryArray), though the first argument, thisArg, is not used by $.when, and null will suffice.


With all that understood and $.when.all() safely installed (as may well be the case), then you can start looking at the other code.

The culprit would appear to be the way in which the Deferreds are resolved/rejected.

Try :

putQueryArray.push($.Deferred(function(dfrd) { // `dfrd` is a reference to the Deferred that will be returned 
    var update = objectStore.put(fav);
    // `dfrd.resolve` and `dfrd.reject` both have `dfrd` already bound in, so .bind() is not required.
    update.onsuccess = dfrd.resolve;
    update.onerror = dfrd.reject;
}).promise());

If you still have problems, then maybe objectStore.put() doesn't work the way you think, or it's just a question of false expectation.

I would expect them to complete upon calling $.when.all(), and execute code within it's corresponding .then() function, yet I discovered via breakpoints that it immediately executes the .put() upon invoking it, ...

The function passed to jQuery.Deferred() is executed synchronously, so objectStore.put(fav); will indeed be executed immediately. It's not clear what alternative behaviour you might want.

Community
  • 1
  • 1
Roamer-1888
  • 19,138
  • 5
  • 33
  • 44
  • I combined your use of passing in the `dfrd` argument into the funciton, and a comment from my question regarding the use of `_.map()` instead of pushing to an array, yet it would appear I'm still seeing the same result. Yes, the `.all()` jQuery extension is defined before this point. To clarify the last part of your answer, I don't necessarily want to halt the execution of `.put()` until I call `$.when`, I just want the `onsuccess` handler to fire and trigger the follow-up `.then()` so that I can track the success or failure of the `.put()`. – Scott Feb 04 '16 at 17:37
  • It seems you want what is known as `.settled()` or `.allSettled()` instead of `.when()`, however jQuery doesn't include such a method. The simplest approach is to resolve the deferred onsuccess with something like `{ value: fav, result: result }`, and onerror with `{ value: fav, error: error })`. Your "follow-up" `.then()` will always fire its success handler and on looping through the results, you have the means of distinguishing success from failure. – Roamer-1888 Feb 04 '16 at 23:12
  • Well... I'm officially an idiot, and the answer was something silly and entirely separate from this. The above code resided within a nested `$.when().then(...)` and since I hadn't returned the $.Deferred object at the end, the updates were happening, but the corresponding `.then()` as outlined above wasn't firing. You still provided a lot of insight though, so thank you! – Scott Feb 04 '16 at 23:34
  • No worries, trust me, I know how easy it is to get sold on a wrong diagnosis. We've all done it. Good luck with your project. – Roamer-1888 Feb 04 '16 at 23:47