Promise.all seems to resolve immediately - javascript

I posted a similar question related to Ember.RSVP.all, but I've found the behavior identical to how Promise.all behaves. Please let me know if I'm breaking any rules by submitting this separately.
I'm trying to use Promise.all in the middle of a chain of promises. The example I have is much simpler than my use, but it demonstrates the issue. In the middle of a chain of promises, I have a set of promises that all need to resolve before the chain can continue - exactly what I understand Promise.all to be for.
Unfortunately, when I return the Promise.all object, the next promise in the chain runs immediately, without waiting for the promises passed to all().
I've set up a js fiddle to demonstrate in the best way that I can think of:
Notice that First and Second both resolve at almost exactly the same time, when Second should be after the 1s promise comes back. Third and fourth follow as expected.
http://jsfiddle.net/vqut9zy2/
Fiddle code looks like this:
function delayAjax(delay) {
return $.ajax({
url: '/echo/json/',
data: {
json: '',
delay: delay,
}
});
}
delayAjax(1).then(function() {
$('#first').addClass('red');
var proms = [delayAjax(1), delayAjax(1)];
return Promise.all(proms).then(function() {
$('#onepointfive').addClass('red');
});
}).then(function() {
$('#second').addClass('red');
return delayAjax(1);
}).then(function() {
$('#third').addClass('red');
return delayAjax(1);
}).then(function() {
$('#fourth').addClass('red');
});
HTML
<div id="first">First</div>
<div id="onepointfive">One point five</div>
<div id="second">Second</div>
<div id="third">Third</div>
<div id="fourth">Fourth</div>

you need to convert jQuery's deferred to a promise first.
function delayAjax(delay) {
return Promise.resolve($.ajax({
url: '/echo/json/',
data: {
json: '',
delay: delay,
}
}));
}
http://jsfiddle.net/evilbuck/vqut9zy2/3/

There is a section in You Don't Know JS: Async & Performance that discusses how to manage untrustable thenables: Trustable Promise?.
It does indeed give Promise.resolve(thenable) as the way to approach this.
I don't know enough to explain why you were witnessing the behavior you were, but Domenic Denicola's article You're Missing the Point of Promises goes into some of the reasons why jQuery's deferreds are not genuine promises, and why they are problematic. At the end of the article, he provides the way to handle untrustable promises in the Q library, which is to use Q.when() (the Angularjs $q service also provides a .when() method for the same purpose):
return Q.when($.ajax({
url: '/echo/json/',
data: {
json: '',
delay: delay,
}
}));

Related

jquery - usage of .done(), .then() and .when() for making ajax requests in a given order

I've been doing a lot of reading about Promises in jquery and avoiding "callback hell" when making multiple ajax requests.
I feel though even after reading all this, there's no simple answer being given to what to use - in terms of .done(), .then() and .when() - in terms of chaining the requests.
I've tried to construct the most basic example to illustrate my point. The code below works exactly as I want it to, but the only thing this relies on is .done() and I can't see where other methods (such as .then() or .when()) fit into this.
So I have created 3 PHP scripts and used PHP's sleep method to artificially delay how long these scripts take to complete. The delays are set as follows:
r1.php - 5 seconds
r2.php - 1 second
r3.php - 3 seconds
The script itself is as simple as this:
<?php
// r1.php
echo "starting r1.php \n";
sleep(5); // Delay execution. Varies as described above for each script
echo "ending r1.php \n";
?>
So if these were run in parallel, the order they'd complete in would be r2.php, r3.php, then r1.php.
But what if we wanted to run them in order (r1.php, r2.php, r3.php) and have jquery wait until each ajax request was made before going on to the next? For example if something in r2.php depends on the result of r1.php etc.
I've written the following - which does exactly that:
$(document).ready(function() {
$.ajax({
url: 'ajax/r1.php',
method: 'get'
}).done(function(response1) {
console.log('After r1.php\n');
console.log(response1);
$.ajax({
url: 'ajax/r2.php',
method: 'get'
}).done(function(response2) {
console.log('After r2.php\n');
console.log('response1:' + response1);
console.log(response2);
$.ajax({
url: 'ajax/r3.php',
method: 'get'
}).done(function(response3) {
console.log('After r3.php\n');
console.log('response1:' + response1);
console.log('response2:' + response2);
console.log(response3);
});
});
});
});
As you can see, the requests are completing in order, and taking the time specified in each PHP script:
Furthermore, due to the scope in which the callbacks are running I can access, for example, response1 (the output of r1.php) in the callback that handles r3.php:
My question is: Under what circumstances are any functions other than .done() (such as .then() or .when() - which are mentioned in countless resources) actually needed to do this type of thing? When would you even need .then() or .when() for this type of scenario?
As far as I understand, .done() is Promise-compatible. I've even replaced .done() with .then() in the code above and the results are exactly the same.
I have read all of the following but I feel like all of these are complicating the issue, of how to run ajax requests in order with jquery:
jQuery deferreds and promises - .then() vs .done()
How do I chain three asynchronous calls using jQuery promises?
https://medium.com/coding-design/writing-better-ajax-8ee4a7fb95f
Please can someone explain this in a way beginners can understand? I am using jquery 3.2.1 so would like a response that's specific to jquery, not vanilla JavaScript.
I originally asked this question but I feel it was badly worded and didn't include enough code. So I've mocked up the code given here to illustrate the exact problem.
The .done() method is only called when the Promise resolves (as opposed to .fail(), which is called when the Promise is rejected).
The .then() method accepts a resolved and a rejected callback and is equivalent to using done/fail together, such that:
$.ajax().then(resolvedCallback(), rejectedCallback());
Is equivalent to
$.ajax().done(sucess()).fail(failure());
The difference being that multiple .then() can more easily be chained together for more complicated Promise resolution.
The .when() method allows you to encapsulate some logic in a Promise/Deffered/Thenable so that you can use .then(), .done(), and .fail() can be used. This is useful if you have some complex or long running logic you want to execute and need to wrap it in a Promise/Deferred easily. It also make it easier to read:
$.when(function(){/*I do something that takes a while*/})
.then(function(){/*I succeed*/},
function(){/*I fail*/})
.then( ... )
.then( ... )
...
You can then end the chain in an .always() to clean up any sort of result or perform some action that must always happen at the end of your chain, regardless of whether or not your promises resolved or were rejected.
Edit: Putting It All Together
Using .when(), we can have code wait unit a number of things are complete before proceeding:
function fetchMe(url) {
return $.ajax({
url: url,
method: 'get'
});
}
$.when(fetchMe('ajax/r1.php'), fetchMe('ajax/r2.php'), fetchMe('ajax/r3.php'))
.then(function(r1, r2, r3) { // Resolve
console.log('response1: ' + r1.data);
console.log('response2: ' + r2.data);
console.log('response3: ' + r3.data);
}, function(){ // Reject!
console.log('Something broke!');
});
In this example, fetchMe() returns the Promise from the $.ajax() call. Using .when(), we tell the script to wait for all three of these to complete before proceeding to the .then(). The input of the resolve method takes deferred items in the order they are in the .when() and, from there, we can retrieve the data from the request.
You can try $.Deffered to chain multiple ajax calls to make them sequential.
Docs:https://api.jquery.com/category/deferred-object/
Similar issue: How to make all AJAX calls sequential?

How to poll an API as part of a $.Deferred()

The below non-functional example should explain what I'm trying to do, I just don't understand the pattern I need to use to accomplish it. I tried googling to understand polling and deferred, but I couldn't find anything I could understand.
I have a function which polls an API, and I want to wait for that polling to return an expected result (waiting for the endpoint to indicate something has changed) before continuing with my main function. What am I doing wrong?
Edit: I should add that what SEEMS to go wrong with the code below is that even though deferred.resolve() eventually gets called, it seems it isn't the same deferred that got returned, so the when never gets activated in main(). I'm guessing it has to do with the timeout, meaning deferred get clobbered on the first repeat. That is my assumption, anyways.
function pollAPI() {
var deferred = $.Deferred();
$.ajax({
url: url,
contentType: 'application/JSON',
method: 'GET'
}).done(function(data){
if (!desiredResult) {
setTimeout(function() {
pollAPI();
}, 1000);
} else {
deferred.resolve();
}
}).error(deferred.reject());
return deferred.promise();
}
function main() {
$.when(pollAPI()).then(function() {
// do something now that the API has returned the expected result
});
You can use chaining of subsequent calls to the pollAPI() function to create a single promise that has others chained onto it. That would work like this:
// utility function to create a promise that is resolved after a delay
$.promiseDelay = function(t) {
return $.Deferred(function(def) {
setTimeout(def.resolve, t);
}).promise();
}
function pollAPI() {
return $.ajax({
url: url,
contentType: 'application/JSON',
method: 'GET'
}).then(function(data) {
// some logic here to test if we have desired result
if (!desiredResult) {
// chain the next promise onto it after a delay
return $.promiseDelay(1000).then(pollAPI);
} else {
// return resolved value
return someValue;
}
});
}
function main() {
pollAPI().then(function(result) {
// got desired result here
}, function(err) {
// ended with an error here
});
}
This has the following benefits:
No unnecessary promise is created to try to surround the ajax call that already has a promise. This avoids one of the common promise anti-patterns.
Subsequent calls to the API just chain to the original promise.
There is no need to use $.when() when you just have a single promise. You can just use .then() directly on it.
All errors automatically percolate back to the original promise.
This uses the ES6-standard .then() (which actually becomes more legitimately standard in jQuery 3.x - though it works in jQuery 1.x and 2.x with its own non-standard quirks) which makes this logic more compatible with other promise-producing async functions.
Also, a number of other retry ideas here: Promise Retry Design Patterns

Adding extra data to result of a promise

I'm using a node library which allows simple http requests to be made and returns a promise - this is working nicely, as it allows several to be made in parallel, and I'm then collecting them later with Promise.all(). However, the http request simply returns a string, and I need to know some extra identifying information about each request. So I though one way to do this was to chain something off the request promise and add that information in. This is the code I've got so far showing one such request being added to the array of promises I'll collect later:
var promises = [];
promises.push(new Promise(function(resolve, reject){
ReqPromise('http://some.domain.com/getresult.php')
.then(function(reqResult) {
resolve({
source: 'identifier',
value: reqResult
});
});
}));
And this is what I get back for this promise when it resolves:
{
source: 'identifier'
value: 1.2.3.4
}
Is this the ideal way to 'tag' a promise result? Or is there something about promises I'm misunderstanding which means I don't need to create an extra promise as above? Note that ReqPromise is in an external library and so it's hard to make it take extra parameters and return them.
Thanks to #Bergi for the link. This is the code I've ended up with, which I agree is much cleaner. I had tried something like this before, but must have made some mistake, as it was returning undefined to Promise.all(). But now is working nicely without extra promises.
promises.push(ReqPromise('http://some.domain.com/getresult.php').then(function(reqResult) {
return {
source: 'identifier',
value: reqResult
};
}));
#dsl101, going one step further than your own answer, it can be useful to .map() an array of data to an array of promises, then associate each data item with its corresponding asynchronously-derived result.
For example, you might start with an aray of URLs.
var data = ['http://path/0', 'http://path/1', 'http://path/2', ...];
var promises = data.map(function(url) {
return ReqPromise(url).then(function(reqResult) {
return {
'source': url,
'value': reqResult
};
});
});
Promise.all(promises).then(function(results) {
// each result has a .value and a corresponding .source (url) property
});
With async/await syntax introduced in ES8, it's possible to await a promise right inside an object literal
This is #dsl101's answer rewritten to use async/await
promises.push((async function() {
return {
source: 'identifier',
value: await ReqPromise('http://some.domain.com/getresult.php')
};
})());
And this is #Roamer-1888's
var data = ['http://path/0', 'http://path/1', 'http://path/2', ...];
var promises = data.map(async function(url) {
return {
'source': url,
'value': await ReqPromise(url)
};
});
var results = await Promise.all(promises)
functions can also be replaced with arrow functions
edit: note that in the pushing promises into array case, the function has to be called first to "become" a promise. in the .map case, the function is called by .map so it has already "become" a promise

Ember.RSVP.all seems to resolve immediately

I'm really hoping that there's something dumb that I'm doing, but I can't seem to find it.
I'm trying to use Ember.RSVP.all in the middle of a chain of promises. The example I have is much simpler than my use, but it demonstrates the issue. In the middle of a chain of promises, I have a set of promises that all need to resolve before the chain can continue - exactly what I understand RSVP.all to be for.
Unfortunately, when I return the RSVP.all object, the next promise in the chain runs immediately, without waiting for the promises passed to all().
I've set up a js fiddle to demonstrate in the best way that I can think of:
http://jsfiddle.net/3a9arbht/3/
Notice that First and Second both resolve at almost exactly the same time, when Second should be after the 1s promise comes back. Third and fourth follow as expected.
Fiddle code looks like this:
function delayAjax(delay) {
return Ember.$.ajax({
url: '/echo/json/',
data: {
json: '',
delay: delay,
}
});
}
delayAjax(1).then(function() {
Ember.$('#first').addClass('red');
var proms = [delayAjax(1), delayAjax(1)];
return Ember.RSVP.all(proms)
}).then(function() {
Ember.$('#second').addClass('red');
return delayAjax(1);
}).then(function() {
Ember.$('#third').addClass('red');
return delayAjax(1);
}).then(function() {
Ember.$('#fourth').addClass('red');
});
Answering based on a response to another question. It appears that while the $.ajax response is indeed "thenable", it is a jQuery deferred object, not a Promise. It's not clear to me why they don't play well together, but the solution is simply to convert the ajax call to a promise:
function delayAjax(delay) {
return Promise.resolve($.ajax({
url: '/echo/json/',
data: {
json: '',
delay: delay,
}
}));
}
With a working fiddle: http://jsfiddle.net/evilbuck/vqut9zy2/3/

Is there a better way of executing a bunch of partially async jQuery functions in a certain order?

I have four functions that must be done in a certain order. All but one of these functions do some asynchronous AJAX stuff in them, however they (necessarily) do other things too. So these are my own functions, which I've turned into deferred promise objects by doing this kind of thing:
function populateOfferSettings() {
return $.Deferred(function (deferred) {
//Whole bunch of stuff happens here, including async AJAX
deferred.resolve();
}).promise();
}
This actually works fine when combined with this:
populateOfferSettings().then(viewReady);
The problem arises when I have more than just two functions in play. In reality I have at least four. I tried the most intuitive solution that came to me and failed:
populateOfferSettings().then(populateSegmentationSettings).then(populateHousehold).then(viewReady);
For reasons I don't understand, all of those functions are executed but there's no waiting for the promises to complete — they just all fire off. So then I tried this, which actually works as expected and each function waits for the previous to resolve:
populateOfferSettings().then(function () {
populateSegmentationSettings().then(function () {
populateHousehold().then(function () {
viewReady();
});
});
});
This feels very verbose though, even if I write a helper function to simplify implementation. I also don't really understand why my initial attempt failed. Is there something I'm missing or is this as simple as I can get it?
You should be able to just keep calling then, but returning the next promise in the chain.
populateOfferSettings()
.then(function () { return populateSegmentationSettings(); })
.then(function () { return populateHousehold(); })
.then(function () { viewReady(); });
In jQuery 1.7.1 .pipe is always what you want. You might as well overwrite .then with .pipe since .then is absolutely useless before jQuery 1.8.
populateOfferSettings()
.pipe(populateSegmentationSettings)
.pipe(populateHousehold)
.pipe(viewReady);

Categories

Resources