I've seen many tutorials on the new EMCA promises advocating avoidance of the "promises" in the jQuery library. They usually say that you can dodge them by doing something like this:
Promise.resolve($.getJSON(url, params)); // voila! the jQuery promise is "gone"!
However, this doesn't really work when I have to chain two async jQuery functions together. How would I chain two getJSON calls (where the second call depends on the first) together without using jQuery's then() or .when()?
Instead, I only want to use Promise.all etc.
I think a similar question problem would be interleaving jquery and EMCA promises?
You can adopt either of two approaches ...
Convert then combine :
var p1 = Promise.resolve($.getJSON(url_1, params_1)); // voila 1!
var p2 = Promise.resolve($.getJSON(url_2, params_2)); // voila 2!
var p3 = Promise.all([p1, p2]).then(...);
Combine then convert :
var p1 = $.getJSON(url_1, params_1);
var p2 = $.getJSON(url_2, params_2);
var p3 = Promise.resolve($.when(p1, p2)).then(...); // voila 1 and 2!
Straightforwardly, either approach will give you a native ES6 promise, p3
, that resolves when both the jQuery promises resolve, or is rejected when either one of the promises fails.
However, you are probably interested in the results of the two getJSON()
calls, and jQuery is awkward in this regard. jQuery's jqXHR promises pass multiple parameters to their success and error callbacks, whereas an ES6 promise will accept just one; the rest will
be disregarded. Fortunately, it's fairly simple to bundle the multiple params together to make a single object. This has to be done in jQuery prior to conversion to ES6.
The "convert then combine" code expands as follows :
var p1 = Promise.resolve($.getJSON(url_1, params_1).then(
function(data, textStatus, jqXHR) {
return { data:data, textStatus:textStatus, jqXHR:jqXHR };
},
function(jqXHR, textStatus, errorThrown) {
return { jqXHR:jqXHR, textStatus:textStatus, errorThrown:errorThrown };
}
));
var p2 = Promise.resolve($.getJSON(url_2, params_2).then(
function(data, textStatus, jqXHR) {
return { data:data, textStatus:textStatus, jqXHR:jqXHR };
},
function(jqXHR, textStatus, errorThrown) {
return { errorThrown:errorThrown, textStatus:textStatus, jqXHR:jqXHR };
}
));
var p3 = Promise.all([p1, p2]).then(
function(results) {
// results[0] will be an object with properties .data, .textStatus, .jqXHR
// results[1] will be an object with properties .data, .textStatus, .jqXHR
},
function(rejectVal) {
// rejectVal will be an object with properties .errorThrown, .textStatus, .jqXHR
}
);
The "combine then convert" approach is slightly trickier as the combined results appear (in jQuery) as an arguments
list, which itself needs to be converted (still in jQuery) to an Array.
var p1 = $.getJSON(url_1, params_1).then(
function(data, textStatus, jqXHR) {
return { data:data, textStatus:textStatus, jqXHR:jqXHR };
},
function(jqXHR, textStatus, errorThrown) {
return { errorThrown:errorThrown, textStatus:textStatus, jqXHR:jqXHR };
}
);
var p2 = $.getJSON(url_2, params_2).then(
function(data, textStatus, jqXHR) {
return { data:data, textStatus:textStatus, jqXHR:jqXHR };
},
function(jqXHR, textStatus, errorThrown) {
return { errorThrown:errorThrown, textStatus:textStatus, jqXHR:jqXHR };
}
);
var p3 = Promise.resolve($.when(p1, p2).then(function() {
return [].slice.call(arguments);// <<< convert arguments list to Array
})).then(
function(results) {
// results[0] will be an object with properties .data, .textStatus, .jqXHR
// results[1] will be an object with properties .data, .textStatus, .jqXHR
},
function(rejectVal) {
// rejectVal will be an object with properties .errorThrown, .textStatus, .jqXHR
}
);
DEMO: resolved
DEMO: rejected
JavaScript promises are interoperable. You can mix them however you want, all proper libraries1 and native promises do accept thenables2 from any implementation anywhere3. If something foreign appears, they just will do Promise.resolve
on it.
So usually you would write your code as if they all used the same promise implementation, and it just works.
However, now you want to ensure that all .then
method calls are using your favourite implementation; or you want to use a non-standard method or feature? For that, you will have to explicitly cast all promises on which you are directly invoking methods - and nothing else.
Some examples:
Promise.all([$.ajax(…), $.ajax(…)]).then(…); // just works!
$.ajax(…) // a jQuery promise
.then(…) // so this would be jQuery `then`, which we don't want.
Promise.resolve($.ajax(…)) // explicit cast
.then(function(data) { // native `then`
return $.ajax(…); // just works!
}) // returns a native promise still
.catch(…) // so we can use its features
1: Yeah, jQuery isn't one of them until version 3.0
2: all jQuery deferreds and promises are such thenables, though
3: Really everywhere you'd expect a promise, in Promise.resolve
, then
callback return values, Promise.all
arguments, …
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With