Reputation: 1310
The Problem:
I've been beating my head against the wall trying to figure out why some code I've written using $.when.apply().then() to resolve multiple promises and then return data seems to fire out of sync, returning an unresolved promise and then resolving the promise after the callback has already been called.
Here is the mock code I've been working with:
format(function(r) {
console.log('Done!', r);
});
function format(callback) {
var promises = [];
var items = [
{model: 'George'}
];
$.each(items, function (i, item) {
var selectedSeries = item.model;
promises.push(getSeriesDescription(selectedSeries));
});
$.when.apply($, promises).then(function (seriesInfo) {
console.log('Calling back');
callback(seriesInfo);
});
}
function getSeriesDescription(series) {
return new Promise(function (resolve, reject) {
getSeriesNotes().then(function (notes) {
console.log('Processing Notes...');
var rNotes;
$.each(notes.data, function () {
if (series !== 'undefined') {
console.log('Checking series ' + this.first_name + ' against ' + series + '...');
if (this.first_name == series) {
console.log('Series found!');
rNotes = this;
return false;
}
}
});
if (rNotes !== undefined) {
console.log('Returning series specific notes...');
resolve(rNotes);
} else {
resolve(notes);
}
});
});
}
function getSeriesNotes() {
return $.ajax({
url: 'https://reqres.in/api/users?delay=2',
dataType: 'json',
type: 'GET',
data: '',
});
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
What I've found:
After tons and tons of debugging I've finally realized that the unexpected behavior seems to be a result of the version of jQuery I'm using. Below I have two fiddles with the exact same code, with different versions of jQuery. (I'm posting the jsfiddles because StackOverflow's one doesn't have a version 3.x of jQuery)
jQuery 2.1.3:
https://jsfiddle.net/persononfire/1epgnfuh/2/
jQuery 3.1.1:
https://jsfiddle.net/persononfire/a9gc0y14/
You'll see by watching the console for each of these that the v3 version returns as I would expect it to, while the v2 version does not.
Expected result:
Processing Notes...
(index):58 Checking series George against George...
(index):60 Series found!
(index):67 Returning series specific notes...
(index):46 Calling back
(index):31 Done! {id: 1, first_name: "George", last_name: "Bluth", avatar: "https://s3.amazonaws.com/uifaces/faces/twitter/calebogden/128.jpg"}
Version 2.1.3 returns the last two lines first, and then everything else in the order I would expect.
The question:
So my question is; Why is there a difference in results between the two versions of jQuery? I wasn't able to find anything in the version documentation that looked to me like it would cause that behavior difference between the two versions of jQuery.
I appreciate any insight into this for my own edification.
Upvotes: 1
Views: 142
Reputation: 1074495
jQuery v2's Deferred and Promise did not conform to the Promises/A+ spec (partially because they predated the current version) and were not reliably inter-operable with native Promises. jQuery v3 fixed them so they would be interoperable with native promises. (Although $.when
isn't entirely compatible.)
Separately: If you're dealing with native promises, I suggest staying as far away from $.when
as possible. Use Promise.all
instead. :-) In fact, my preference is either to stay entirely within the jQuery Deferred/promise ecosystem or entirely outside it (by converting any jQuery Deferreds/Promises to real ones early).
Here's a rather simpler example of $.when
not working with native promises.
jQuery v2, not working:
function timeout(value, delay) {
console.log("creating promise for " + value);
return new Promise(resolve => {
setTimeout(() => {
console.log("resolving promise for " + value);
resolve(value);
}, delay);
});
}
$.when(
timeout(1, 100),
timeout(2, 200),
timeout(3, 300)
).then(result => {
console.log("then on $.when: ", result);
});
.as-console-wrapper {
max-height: 100% !important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
jQuery v3, working although note that the return value is just the value of the first promise (because $.when
's call to the then
callback is non-standard: it passes each promise result as a discrete argument):
function timeout(value, delay) {
console.log("creating promise for " + value);
return new Promise(resolve => {
setTimeout(() => {
console.log("resolving promise for " + value);
resolve(value);
}, delay);
});
}
$.when(
timeout(1, 100),
timeout(2, 200),
timeout(3, 300)
).then(result => {
console.log("then on $.when: ", result);
});
.as-console-wrapper {
max-height: 100% !important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Side note: There's no reason for new Promise
in getSeriesDescription
, getSeriesNotes().then
already gives you a promise you can use. And amusingly, if you fix that, it works even in jQuery v2:
format(function(r) {
console.log('Done!', r);
});
function format(callback) {
var promises = [];
var items = [{
model: 'George'
}];
$.each(items, function(i, item) {
var selectedSeries = item.model;
promises.push(getSeriesDescription(selectedSeries));
});
$.when.apply($, promises).then(function(seriesInfo) {
console.log('Calling back');
callback(seriesInfo);
});
}
function getSeriesDescription(series) {
return getSeriesNotes().then(function(notes) {
console.log('Processing Notes...');
var rNotes;
$.each(notes.data, function() {
if (series !== 'undefined') {
console.log('Checking series ' + this.first_name + ' against ' + series + '...');
if (this.first_name == series) {
console.log('Series found!');
rNotes = this;
return false;
}
}
});
if (rNotes !== undefined) {
console.log('Returning series specific notes...');
return rNotes;
} else {
return notes;
}
});
}
function getSeriesNotes() {
return $.ajax({
url: 'https://reqres.in/api/users?delay=2',
dataType: 'json',
type: 'GET',
data: '',
});
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Upvotes: 3