Outer variables not being altered inside Promise.then function - javascript

On node while using thinky.js, I am trying to iterate through a loop and add each item to an array. This however, for some reason is not working.
In another place, it is indentical and working, just without a Promise.then function. Why is this not working?
var fixedItems = [];
for (i in tradeItems) {
var item = tradeItems[i];
Item.get(item["id"]).run().then(function(result) {
var f = { "assetid": result["asset_id"] };
console.log(f); // WOrks
fixedItems.push(f); // Doesn't work
});
}
console.log(fixedItems); // Nothing

A Promise represents the future result of a task. In this case you're logging fixedItems before your tasks (the calls to Item.get) have finished working. In other words, the then functions haven't run yet so nothing has been put into fixedItems.
If you want use fixedItems once it contains all of the items, you'll need to wait for all of the promises to resolve.
How you do that depends on the Promise library you're using. This example, with Promise.all, works with many libraries including native ES6 Promises:
// Get an array of Promises, one per item to fetch.
// The Item.get calls start running in parallel immediately.
var promises = Object.keys(tradeItems).map(function(key) {
var tradeItem = tradeItems[key];
return Item.get(tradeItem.id);
});
// Wait for all of the promises to resolve. When they do,
// work on all of the resolved values together.
Promise.all(promises)
.then(function(results) {
// At this point all of your promises have resolved.
// results is an array of all of the resolved values.
// Create your fixed items and return to make them available
// to future Promises in this chain
return results.map(function(result) {
return { assetid: result.asset_id }
});
})
.then(function(fixedItems) {
// In this example, all we want to do is log them
console.log(fixedItems);
});
Recommended reading: the HTML5 rocks intro to Promises.

Your problem is that you are calling console.log(fixedItems) before any of the promises in the loop have finished executing. A better way of doing this that would also solve the asynchronous problem is to put all the item IDs in an array first and retrieve all the items in a single query, which is also more efficient on the database side.
var itemIds = tradeItems.map(function(item) {
return item.id;
});
var fixedItems = [];
//you would need to write your own getItemsById() function or put the code
//to get the items here
getItemsById(itemIds).then(function(items) {
items.forEach(function(item) {
var f = { "assetid": result["asset_id"] };
fixedItems.push(f);
});
whenDone();
});
function whenDone() {
//you should be able to access fixedItems here
}
I couldn't easily find how to look up multiple records by ID in a single query with thinky, but I did find this page which might help:
http://c2journal.com/2013/01/17/rethinkdb-filtering-for-multiple-ids/
While this would be my preferred way of solving this problem, it would also be possible to still use multiple queries and use a promise chain to wait for them all to be resolved before continuing to your subsequent code. If you wanted to go that route, check out this link: http://promise-nuggets.github.io/articles/11-doing-things-in-parallel.html. (Note: I haven't personally used Bluebird, but I think the Bluebird example in that link may be out of date. The map method appears to be the current recommended way to do this with promises: https://stackoverflow.com/a/28167340/560114.)
Update: Or for this latter option, you can just use the code in joews's answer above.

Related

Making while for checking the value is correct or not

I have 2 variable and i want to do something when they changed to same value like each other.
So this is my code:
var val_1,val_2;
for(i=0;i<=5;i++){
readyforgo++;
$.post("somewhere.php",{
something: something
},function(data, status){
readytogo++;
});
}
if(readyforgo==readytogo){
alert(1);
}
Its not gonna return 1 because my if is working faster than my $.post i mean i want some code like this :
function checker(){
if(readyforgo==readytogo){
alert(1);
}else{
setTimeout(function(){checker();},100);
}
}
checker();
Its working as well but i looking for something better i mean i don't want to use any code like that because its using cpu and ram until trying to get readyforgo==readytogo do we have code like this?
i don't like to use setTimeout or setInvertal or something like that.
You could use Promise.all(promises).
Your $.post would need to be adapted to create a promise unless it already returns one. (See comment from georg)
You would put each promise into an array.
Promise.all will be invoked after all async calls complete.
const promises = []
for(let i=0; i<5; i++){
// assuming your post returns a promise
promises.push($.post("somewhere.php"))
}
Promise.all(promises).then(results=>{
// your code here
})
If your code is in an async block, you can do the following and avoid nested functions:
results = await Promise.all(promises)
// your code here
})
This pattern of waiting on all promises is very common after dispatching multiple async calls.
If you want to check when all the posts have either been resolved/rejected you could take advantage of how jQuery's deferred objects (similar to promises) work. Create an array of jQuery deferred objects and then use when to wait until they've all resolved/rejected.
// Create an array of POST deferred objects
function getDeferreds() {
const deferreds = [];
for (let i = 0; i <= 5; i++) {
deferreds.push($.post("somewhere.php", { something: something }));
}
return deferreds;
}
// $.when works similarly to Promise.all
$.when.apply(null, getDeferreds()).then(data => {
alert(1);
});
Here's a good explanation why apply has been used in this example.

Resolving promises synchronously with Q.all(promises)

I am using the Kriskowal Q library to handle promises.
I wrote the following function to wait until all promises have been resolved.
function multiplePromises(targetArray) {
var promises = {};
for (var i=0; i<targetArray.length; i++) {
var promise = singlePromise(); // any async. function
promises[i] = promise;
};
return Q.all(promises);
};
and I call it as follows:
multiplePromises(targetArray).then(
function(success){
console.log("success");
},
function(error){
console.log("error", error);
}
);
I was wondering however whether there is an order in which the promises are resolved (e.g. is it synchronous?). I.e. does the function wait to trigger the next promise i+1 until promise i is resolved? Or alternatively is it like with all other async. methods, that it actually fires all the single promises and just waits until they are all done?
If the second is the case, how would one rewrite this function to make sure that promise i+1 is ONLY triggered once promise i has been resolved?
Update: test
I did a test and put:
promises[i] = i;
to check whether it resolves sycnhronously and it seems the case. However, it could be just that my async function is fast enough to actually resolve it that quick. Does this seem right?
there are several ways to achieve what you want
Minimal change to your code would be
function multiplePromises(targetArray) {
var promises = [];
var p = Promise.resolve(); // I don't know the Q equivalent of this
for (var i=0; i<targetArray.length; i++) {
p = p.then(function() {
return singlePromise();
});
promises[i] = p;
};
return Q.all(promises);
};
The promises will be executed by Q in the order you declare them, but there's no way to ensure the return order will be the same. That is how async works.
If you want them to resolve in order, the only way I can think of is calling them one after the other instead of doing it in a batch.
This other response will provide more info and some solutions:
https://stackoverflow.com/a/36795097/7705625
Promises in theory could be resolved in order (and it could be easy to write an example in which they would) but you should never count on that.
The whole point of functions like Promise.all() (or Q.all() or Bluebird.all() etc.) is that if waits for all of the promises to get resolved, no matter what is the order of that, and as soon as they are all resolved then the promise that the Promise.all() itself returns gets resolved with an array of values that the original promises resolved to.
In that array you will get always the same order as the order of promises in the array that was an argument to Promise.all(). But the order in time in which the original promises were resolves is not known and is not relevant, as that will have no effect on the result of using Promise.all() whatsoever.

Working with 1 or more jQuery promises

I'm making either 1 or more REST/ajax calls to validate some user information. The rest calls are working well and the information is coming back. The issue I'm facing isn't with that part of the code, which looks something like this.
function ensureUsers(recipients){
var promises = [];
for(var key in recipients){
var payload = {'property':recipients[key]};
promises.push( $.ajax({...}));
}
return $.when.apply($,promises);
}
....
ensureUsers(users) // users is an array of 1 or more users
.done(function(){
console.log(arguments);
)}
If there is more than one user in the initial array, then the arguments in my .done code are structured like this:
[[Object,"success",Object],[Object,"success",Object]...]
I can then iterate over each result, check the status, and proceed.
However if there is only one user in the initial array then .done gets arguments like this:
[Object,"success",Object]
It seems strange to me that the structure of what is returned would change like that. I couldn't find anything about this specific a problem, so I hacked together a solution
var promises = Array.prototype.slice.call(arguments);
if(!Array.isArray(promises[0])){
promises = [promises];
}
Is that really the best I can hope for? Or is there some better way to deal with the returned promises from 1 or more ajax calls in jQuery?
It seems strange to me that the structure of what is returned would change like that.
Yes, jQuery is horribly inconsistent here. When you pass a single argument to $.when, it tries to cast it to a promise, when you pass multiple ones it suddenly tries to wait for all of them and combine their results. Now throw in that jQuery promises can resolve with multiple values (arguments), and add a special case for that.
So there are two solutions I could recommend:
Drop $.when completely and just use Promise.all instead of it:
var promises = [];
for (var p of recipients) {
…
promises.push( $.ajax({…}));
}
Promise.all(promises)
.then(function(results) {
console.log(results);
})
Make each promise resolve with only a single value (unlike $.ajax() that resolves with 3) so that they don't get wrapped in an array, and $.when will produce consistent results regardless of number of arguments:
var promises = [];
for (var p of recipients) {
…
promises.push( $.ajax({…}).then(function(data, textStatus, jqXHR) {
return data;
}) );
}
$.when.apply($, promises)
.then(function() {
console.log(arguments);
})
It appears this functionality is working as currently documented. When you have multiple deferreds passed to $.when it creates a new Promise object that is resolved with the results of each of the results of the passed in deferreds. When you only pass in one, it returns the deferred that you passed in, thus only returning the result instead of an array of results.
I'm not sure if it is any better than your current solution, but you could force it to always have multiple deferreds by having a "fake" deferred that you skip when evaluating the results.
I.E.
function ensureUsers(recipients){
var promises = [$.Deferred().resolve()];
for(var key in recipients){
var payload = {'property':recipients[key]};
promises.push( $.ajax({...}));
}
return $.when.apply($,promises);
}
You could also potentially make it so the placeholder deferred is resolved with the same structure as what you expect in your real results so it would just appear that the first response is always a success.

can't get $q.all work on multiple promises

I really struggle with that... I have a function in my controller generating a certain amount of promises, passed to another function which also generate promises...
angular.forEach($scope.items,function(value,key) {
myService.getPage(item).then(function(data) {
myFunction(data)
});
function myFunction(singlePage) {
myService.getPage(singlePage.next).then(function(data) {
myFunction(data)
});
}
What i want to achieve is to somehow know when all promises will be resolved
I tried to wrap my promises in an array and pass it to $q.all in all kind of configuration but without success..Any kind of help would be really cool !! Thanks
Ignoring the infinite recursion loop you've created, let's do this...
var allPromises = [];
angular.forEach($scope.items,function(value,key) {
allPromises.push(myService.getPage(value));
});
$q.all(allPromises)
.then(function (arrayOfResults) {
// do something
});
One thing to note, you are passing in value and key, but using "item". You need to be using value.

Nested Promises

I have a function that does a series of asynchronous actions that in turn execute loops of other asynchronous actions. I'd like to know when everything is complete. It seemed like a great time to get my head wrapped around promises.
My code in the before-promise state boils down to something like this (hopefully in the simplification process I haven't rendered the example useless):
myClass.prototype.doMaintenance = function() {
var types = ['choreType1', 'choreType2'];
types.forEach(function(choreType) {
// find all chores of the type with score 0 (need to be done)
redisClient.zrangebyscore('chores:'+choreType, 0, 0, function(err, chores) {
if (!err) {
chores.foreach(function(chore) {
doChore(chore, function(err, result){
// chore complete!
});
})
}
});
});
}
I go through a loop, and for each item in the loop I make an asynchronous database call, and loop through the results returned, making another asynchronous call for each result. Using callbacks to pass notification that all chores are done seems like it would be ugly at best. Therefore my goal: construct a promise that will resolve when all the chores are done.
I'm facing two difficulties. One is simply getting the promise syntax right. I'll show you what I've tried below. First through, an issue that may render this insolvable: say that the first database query comes back with a single chore. I (somehow) put that as part of the overall "all chores done" promise. Now I go back to get a list of the next type of chore. What if in the meantime the first chore is completed? The all-chores-done promise will be satisfied, and will resolve, before the rest of the chores are added.
I'm using the Q library in a node.js environment. I use Redis but it could be any asynch data source.
myClass.prototype.doMaintenance = function() {
var types = ['choreType1', 'choreType2'];
var typePromises = [];
types.forEach(function(choreType) {
// find all chores of the type with score 0 (need to be done)
Q.npost(redisClient, 'zrangebyscore', ['chores:'+choreType, 0, 0]).done(chores) {
var chorePromises = [];
chores.foreach(function(chore) {
chorePromises.push(doChore(chore)); // doChore returns a promise
});
typePromises.push(Q.all(chorePromises));
});
});
return Q.all(typePromises); // at this point, typePromises is empty. Bummer!
}
What I've been trying to build (not quite there yet) is a promise that is a collection of typePromises, which in turn are collections of chorePromises.
I think what I need is a structure that says "I promise to get you the all-chores-done promise as soon as it's available." This is starting to make my head explode. Any guidance (including using a different pattern entirely) would be greatly appreciated.
You are constructing the list of typePromises asynchronically - and when you call Q.all(typePromises), it is still empty. Instead, you immediately need to return a promise for the database result which you can immediately collect into the list. If you don't know yet what the return value of those promises will be - no worries, use then to compose the tasks like getting Q.all(chorePromises) after a redis result has arrived.
I also would propose using map instead of pushing to an array in an each loop - this also helps to make sure that the promises are constructed immediately.
myClass.prototype.doMaintenance = function() {
var types = ['choreType1', 'choreType2'];
var typePromises = types.map(function(choreType) {
// find all chores of the type with score 0 (need to be done)
return Q.npost(redisClient, 'zrangebyscore', ['chores:'+choreType, 0, 0]).then(function(chores) {
var chorePromises = chores.map(doChore); // doChore returns a promise
return Q.all(chorePromises);
}); // then returns a promise
});
return Q.all(typePromises);
}

Categories

Resources