In the past I've used the jQuery deffered/promises, but I'm trying to move to javascript Promises. I have a function that needs to make several network calls in sequence and then several that can happen in any order, but all need to resolve before moving forward. I start with the sequential calls and they go in order, but then with the results of the second call I need to iterate over those results and make a variable number (at the moment 6) of further calls. I don't care what order those are done in, but do need to wait for all of them to resolve before I proceed. I thought this pattern would work. But it doesn't.
function doLotsOfStuff(){
firstNetworkCall()
.then(function(data){
// do stuff with data
return secondNetworkCall();
})
.then(function(data){
// do stuff with data
var promises = data.map(function(item){
// All of these calls (happens to be 6)
// need to be done before I continue
return thirdIndependentCall(item.Info);
});
// at this point I see [Promise, Promise, ....]
// all are unresolved
return Promise.all(promises);
})
.then(function(results){
// executes immediately after the Promises.all() line
// none of them are resolved
// results is just one unresolved promise
});
}
I can chain the final step onto the Promises.all() like this
return Promise.all(promises)
.then(function(results){
// this works!
})
But if I want to chain more things after that I have to keep stepping them in. Seems like I'm missing some piece of information.
EDIT
I had copied over a typo in my simplified code which isn't in my actual code. The suggestion to add a .catch() in my chain was a good one and I tried it. Now it throws firstNetworkCall(...).then(...).then(...).catch is not a function.
I think the problem is that some of my code (somewhere) is still relying on jQuery.Deffered and that doesn't play well with Promise.
It is good to know that my initial pattern should work -- if I'm consistent with what kind of async handling I'm working with.
Change this:
Promises.all(promises)
to this:
Promise.all(promises)
What you have is throwing an exception (because Promises is not defined which is caught by the .then() and then rejects the promise chain.
This is a classic reason why you should always have a .catch() at the end of your chain because if you were logging that rejection, it would have probably told you what the error was.
If you want to convert any jQuery promises to ES6 promises so you can use .catch() anywhere or to just guarantee consistent behavior, you can wrap them with Promise.resolve() as in:
Promise.resolve(firstNetworkCall()).then(...).catch(...)
or this:
var promises = data.map(function(item) {
// All of these calls (happens to be 6)
// need to be done before I continue
return Promise.resolve(thirdIndependentCall(item.Info));
});
// at this point I see [Promise, Promise, ....]
// all are unresolved
return Promise.all(promises);
And, keep in mind that some newer jQuery versions are more compatible with ES6 promises than others.
Related
I have been using Promises and async/await, they are pretty much the same thing right? Usually what I would do is wrap my promise and return it etc.
function someFetchThatTakesTime(){
// Promisify the request.
return new Promise((resolve, reject) => {
if(allGood){
resolve();
}else{
reject();
});
}
Then I can do:
someFetchThatTakesTime()
.then(console.log('all good.')
.catch(console.log('some error occured.');
or I can do the:
async function wrapMyFetch() {
try {
// Make the async call.
data = await someFetchThatTakesTime();
return data;
} catch(err) {
// Propagate the exception up stream.
throw err;
}
}
(async () => {
let response = await wrapMyFetch();
// Do stuff with the response.
})();
I guess, pretty clear so far.
However, I have recently encountered situations where my application does not care about waiting for the results to be fetched and the data containers to be updated etc. Let's say there is one larger loop that runs infinitely and any Promised requests simply will fill-in the gaps as the application runs.
In that case, we don't really need the async/await pattern right? We just want to move forward with our loop and stones will fall in place behind us, whenever they are ready to fall in place (or not, in case of an error).
I would like to clarify this: async/await will just force things to run linearly, right? But if we do not want linearity, and we are ok with spawned tasks - Promises - to finish their thing in the background in some time when we are resuming with our run-cycle, we don't need that async/await pattern? Is that correct?
I have been using Promises and async/await, they are pretty much the same thing right?
Well, async/await are helpful syntax built on top of promises. Async/await relies on promises in order to work. So, I wouldn't quite call them the same thing, but you can code identically functioning code with await surrounded by try/catch and .then().catch(). You can use either. Both methods rely on promises.
However, I have recently encountered situations where my application does not care about waiting for the results to be fetched and the data containers to be updated etc. Let's say there is one larger loop that runs infinitely and any Promised requests simply will fill-in the gaps as the application runs.
In that case, we don't really need the async/await pattern right? We just want to move forward with our loop and stones will fall in place behind us, whenever they are ready to fall in place (or not, in case of an error).
There is no rule that the caller has to pay attention to a returned promise or has to wait for it before going on with their task. So, if you want to "just let the stones fall as they may in the background" as you say and that's appropriate for your application, you can code that way.
This is often referred to as "fire and forget" coding where you start some asynchronous operation that is going to do something on its own and you don't need to pay attention to when it finishes or if it had an error because that is of no consequence to the calling code.
But, there is a rule that you can't let a promise rejection go unhandled. So, if you're returning a promise and the caller isn't going to do anything with it, then you have to make sure that any rejections are handled by the operation itself (with .catch()) so that the only thing that ever gets returned back is a resolved promise. Even if all you do is notice the error and do nothing with it, you have to catch the error.
async/await will just force things to run linearly, right?
Not globally, no. await makes the code in the async function it's used in wait for the promise you pass it to settle, but that only affects the function you use it in, not anything calling that function.
async functions are syntactic sugar for writing a function that returns a promise. await is syntactic sugar for consuming promises. Together, they dramatically simplify using promises (in particular by making rejections errors that automatically propagate through the call tree).
More specifically, an async function runs its code synchronously until the first await or return (or until code runs off the end of the function), at which point it returns a promise. Its logic then waits for the promise to settle, and continues; eventually it settles its promise based on what happened to the last promise it awaited or returned (or fulfills it with undefined if code execution runs off the end).
If you don't want to wait for a promise to settle, you don't have to, not even in an async function. Just don't use await on it. For instance, assume we have this wrapper for fetch that gets JSON for us (and fixes the API footgun):
async function fetchJSON(...args) {
const response = await fetch(...args);
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
return response.json();
}
Here's an async function using it that does an initial query to get a list of things to fetch, and then fetches all of those things in parallel::
async function fetchList(listUrl) {
const list = await fetchJSON(listUrl);
return Promise.all(list.map(item => fetchJSON(itemUrl)));
}
Notice how fetchList uses await to wait for the list of things to fetch, but then doesn't wait for the items; it just returns the promise from Promise.all for the list of items it starts fetching.
Also note how await within fetchJSON makes fetchJSON's logic wait for the fetch promise to settle, but doesn't make the caller calling fetchJSON wait unless that caller uses await. fetchList only awaits the first call (to get the list), it doesn't wait for the item from the list.
I have a methodology question. Currently I am using $q.all to capture multiple promises in a single return, then processing all the results as single request.
ie: $q.all([promise1(),promise2(),promise3(),promise(4),promise5()])..then(function(response){ ...}
However, I am noticing that sometimes different promises are being returned at significantly different time frames. All the promises are http calls to third party sites. When any particular promise is delayed by say 8 seconds...or 14 seconds, then the final results of all promises are delayed by that same duration. The weakest...rather 'slowest'...link syndrome.
What is another method that I can use to call all the promises at the same time yet still allow for results to be processed, and viewed by the user, as they come in? To NOT wait on all of them to be returned before processing them all at once?
As suggested in the comments you could just use them separately, but if you really want to call them all at the same time and handle them in one promise, you can use the notify-callback of the promise. I've created an extension to $q that uses this notify() function to resolve the promises as one:
app.run(function($q){
$q.each = function(promises){
var deferred = $q.defer();
promises.forEach(function(promise){
promise.then(function(data){
deferred.notify(data);
});
});
return deferred.promise;
};
});
This is a pretty naive implementation that doesn't handle errors for instance, but it gives you an idea of what's involved.
Then you'd just use it like this:
var promises = [promise1(),promise2(),promise3(),promise(4),promise5()];
$q.each(promises).then(null, null, function(data){
console.log(data); // This is called when each promise resolves.
});
Here's a Plunker showing this in action
Keep in mind that the methods like then() or $q.all() do not determine how the promises execute; they merely establish the semantics for when you want to respond. What I mean is, the promises start running when you create them even though a fluent reading of myPromise.then(doSomething) might seem to imply otherwise.
Combine that with the fact that $q.all doesn't so much "combine promises" as it creates a new promise that resolves or rejects based on the resolution or rejection status of the original promises; and that a promise is not limited to only a single handler...
promise1.then(function () {
//stuff that only cares about promise1 and needn't wait for the others
});
$q.all(promise1, promise2, ...).then(function () {
//stuff that can't be done until everything finishes
});
What is the correct way to use $q.all in a promise chain then clause?
Here is some pseudo-type code to illustrate what I'm trying to do.
function nestedPromise(val)
{
return aPromiseReturningFunction(val)
.then($q.all(val.arrayProperty.map(function(anotherVal)
{
return anotherPromiseReturningFunction({
prop1: anotherVal.prop
});
})));
}
Expectation: if any of the promises returned in the call to $q.all reject, the promise returned by nestedPromise will reject.
Actual: the promise returned by nestedPromise is resolved, even though one of the promises returned in the call to $q.all rejected.
I can't post everything I have tried to get this to work because I feel like I have tried literally everything...catches all over the place, using deferred, etc. I think at one point I got things "working" by using a ton of deferrals and catches, but it was really ugly and it definitely didn't work because I knew what I was doing.
Is it expected behavior for all of the function calls in my $q.all call to run in parallel? That seems to be what is currently happening because even after one promise rejects, the remainder continue to run. For the time being, I can deal with this behavior although it doesn't seem quite right and I'd like to know how to halt execution after the first rejection is encountered.
then() expects a function to be passed to it. You're currently passing another Promise object in here
then($q.all(val.arrayProperty.map(function(anotherVal)
You should instead be doing this
function nestedPromise(val) {
return aPromiseReturningFunction(val)
.then(function() {
return $q.all(val.arrayProperty.map(function(anotherVal) {
return anotherPromiseReturningFunction({
prop1: anotherVal.prop
});
})
});
}
I'm trying to execute an asynchronous routine for a bunch of items in a list that I get from a database, but I'm having trouble understanding how promise.all works and what it does.
Here is the code I'm using right now:
/**
* Queues up price updates
*/
function updatePrices() {
console.log("~~~ Now updating all listing prices from Amazon API ~~~");
//Grabs the listings from the database, this part works fine
fetchListings().then(function(listings) {
//Creates an array of promises from my listing helper class
Promise.all(listings.map(function(listing){
//The promise that resolves to a response from the routine
return(listing_helper.listingPriceUpdateRoutine(listing.asin));
})).then(function(results){
//We want to log the result of all the routine responses
results.map(function(result){
console.log(result);
});
//Let us know everything finished
console.log("~~~ Listings updated ~~~");
}).catch(function(err){
console.log("Catch: ", err);
});
});
}
Right now, the only thing I get in the log is
~~~ Now updating all listing prices from Amazon API ~~~
I've tried adding a logging piece into the routine that is called and the routines all run successfully and log what they should, but promise.all.then doesn't execute.
I've tried just doing:
Promise.all(bleh).then(console.log("We did it"));
and that worked, but when I put a function in the Then, nothing runs.
Please help!
Promise.all() itself is pretty simple. You pass it an array of promises. It returns a new promise that will resolve when all the promises in your array resolve or will reject when any individual promise in the array rejects.
var pAll = Promise.all([p1, p2, p3]);
pAll.then(function(r) {
// all promises resolved
// r is an array of results
}, function(err) {
// one or more promises rejected
// err is the reason for the first promise that rejected
});
Some reasons that Promise.all() might not work in your code:
You aren't passing an array of promises to it.
Some of the promises in the array you pass never resolve or reject thus Promise.all() never resolves/rejects its master promise
You aren't properly passing callbacks to .then() handlers where you should be
You aren't properly returning promises from internal functions so they propagate out or chain properly
Your example:
Promise.all(bleh).then(console.log("We did it"));
Is wrong. You must pass a function reference to .then() like this:
Promise.all(bleh).then(function() {
console.log("We did it")
});
In your case, the console.log() would execute immediately and not wait for the promises to be resolved.
In your detailed code are you 100% sure that:
listing_helper.listingPriceUpdateRoutine(listing.asin)
is returning a promise? And, that that promise will get resolved or rejected properly?
Note to Readers - If you read all the comments, you can see that the OP's actual issue was not with Promise.all(), but they were getting rate limited for sending requests too quickly to the target host. Since that should have been propagating a request error back which should have been easily visible, the OP apparently also has a problem with error handling or propagation which is likely in code that was not disclosed here.
I have a number of async tasks that need to be completed, so I'm using promises.
I need to detect when each one of the promises has been executed (both resolved and rejected). I must not continue execution until that point.
I was using something like this:
$.when(promise1, promise2, ...).always();
But this code is wrong, because the when method has lazy evaluation, and it returns as soon as one of the promises fails. So the always callback also runs as soon as one of the promises fail.
I was thinking in coding a workaround, but this use case is so common that maybe somebody has done it already, or maybe there's even a way of doing this using just jQuery (if not, it would be nice to add a Promise.whenNonLazy or a Promise.when(promise1, promise2, ..., false) in the future.
Is this possible?
More sophisticated promise libraries have an allSettled() function like Q or Promise.settle like Bluebird.
In jQuery, you could implement such a function yourself as well and extend the $ namespace with it, but that will only be necessary if you need it often and performance-optimized.
A simpler solution would be to create a new promise for each of the ones you are waiting for, and fulfilling them even when the underlying one is rejected. Then you can use $.when() on them without problems. In short:
// using Underscore's .invoke() method:
$.when.apply(null, _.invoke(promises, "then", null, $.when)).done(…)
More stable:
$.when.apply($, $.map(promises, function(p) {
return p.then(null, function() {
return $.Deferred().resolveWith(this, arguments);
});
})).then(…);
You might change the then callbacks a bit to distinguish between fulfilled and rejected results in the final done.
Smithy,
First let's assume your promises are in an array.
var promises = [....];
What you appear to want is .when() applied to some transform of these promises, such that any rejected promise is converted to resolved, whilst being transparent to promises that are already resolved.
The required operation can be written very succinctly as follows :
$.when.apply(null, $.map(promises, resolvize)).done(...);
//or, if further filtering by .then() is required ...
$.when.apply(null, $.map(promises, resolvize)).then(...);
where resolvize is the transform mechanism.
So what should resolvize(), look like? Let's exploit the characteristics of .then() to make the distinction beteween a resolved and a rejected promise, and respond accordingly.
function resolvize(promise) {
//Note: null allows a resolved promise to pass straight through unmolested;
return promise.then(null, function() {
return $.Deferred().resolve.apply(null, arguments).promise();
});
}
untested
With resolvize in some outer scope, it can be made available to be used in a $.when.apply($.map(promises, resolvize)) expression wherever it is needed. This is most likely adequate, without going to the extent of extending jQuery with a new method.
Regardless of how the transform is achieved, you end up with a potential issue; namely knowing for each argument of the .done() callback, whether its corresponding promise was originally resolved or rejected. That's the price you pay for converting rejection to resolution. You may, however, be able to detect the original status from the parameter(s) with which the original promises were resolved/rejected.
That's an interesting property of always - I hadn't expected that behaviour.
I suppose you could use a master, top-level deferred to monitor the states of the main deferreds, which is resolved only once the main deferreds are all either resolved or rejected. Something like:
//set up master deferred, to observe the states of the sub-deferreds
var master_dfd = new $.Deferred;
master_dfd.done(function() { alert('done'); });
//set up sub-deferreds
var dfds = [new $.Deferred, new $.Deferred, new $.Deferred];
var cb = function() {
if (dfds.filter(function(dfd) {
return /resolved|rejected/.test(dfd.state());
}).length == dfds.length)
master_dfd.resolve();
};
dfds.forEach(function(dfd) { dfd.always(cb); });
//resolve or reject sub-deferreds. Master deferred resolves only once
//all are resolved or rejected
dfds[0].resolve();
dfds[1].reject();
dfds[2].resolve();
Fiddle: http://jsfiddle.net/Wtxfy/3/