javascript - have promises call at end of for loop - javascript

I have this code and I want to make the code wait until the asyncrnous query called prom has finessed before restarting the first for loop. So the array will be reset, before it starts the first for loop again.
items = [];
var promises = [];
for (var i = 0; i < userArray.length; i++) {
items.length = 0;
for (var i2 = 0; i2 < test.length; i2++) {
var UserFavourite = Parse.Object.extend("UserFavourite");
var queryUserFav = new Parse.Query(UserFavourite);
queryUserFav.equalTo('item', test[i2].get('item'));
queryUserFav.equalTo('school', test[i2].get('school'));
queryUserFav.equalTo('user1', userArray[i])
var prom = queryUserFav.find().then(function(res) {
for (var i3 = 0; i3 < res.length; i3++){
var item = res[i3];
var itemName = item.get('item');
items.push(itemName);
console.log(items)
}
return items;
});
promises.push(prom);
}
//return Parse.Promise.when.apply(Parse.Promise, promises); I have tried it here but
// this just stops the first for loop after its first loop
}
return Parse.Promise.when.apply(Parse.Promise, promises);

What you're trying to do is have a chain of promises, one for each item in an array.
If would be nice if javascript had an equivalent of .NET's await keyword, where you could go
await Parse.Promise.when(promises)
and then it allowed the promise code to run then returned back to running any code after the await. But Javascript doesn't give this to us.
Another approach is to maintain an index variable. After every set of queries is processed, you increment the index variable and process the next set of values.
function parseForUser(user) {
var promises = [];
for (var i2 = 0; i2 < test.length; i2++) {
var items = [];
var UserFavourite = Parse.Object.extend("UserFavourite");
var queryUserFav = new Parse.Query(UserFavourite);
queryUserFav.equalTo('item', test[i2].get('item'));
queryUserFav.equalTo('school', test[i2].get('school'));
queryUserFav.equalTo('user1', user)
var prom = queryUserFav.find().then(function(res) {
for (var i3 = 0; i3 < res.length; i3++){
var item = res[i3];
var itemName = item.get('item');
items.push(itemName);
console.log(items)
}
return items;
});
promises.push(prom);
}
return Parse.Promise.when(promises);
}
function parseUserArray(userArray) {
var returnPromise = new Parse.Promise(); // Do you have to call it via new?
//The documentation isn't clear.
var index = 0;
var doNext = function() {
if(index < userArray.length) {
var promise = parseForUser(userArray[index++]);
promise.done(doNext);
} else {
returnPromise.resolve();
}
}
doNext();
return returnPromise;
}
var parseUserArrayPromise = parseUserArray(userArray);

FWIW ...
This solution differs from #AndrewShepherd's chiefly in that here we take double advantage of the promise returned by asyncProcessUser().
firstly for flow control - the asynchronous sequencing of the inner loop
secondly for delivering an array of results, from which the final array of results is built, avoiding the need for an outer promises array.
function parseUserArray(userArray, test) {
// This function is the original inner for() loop, now expressed as a .map(),
// (plus peripheral wrapping and chaining).
// It asynchronously processes a single user against all test items,
// and returns a promise of an array of results.
// The promise resolves when all the individual .finds complete.
function asyncProcessUser(user) {
return Parse.Promise.when(test.map(function(dataItem) {
return (new Parse.Query(Parse.Object.extend("UserFavourite")))
.equalTo('item', dataItem.get('item'))
.equalTo('school', dataItem.get('school'))
.equalTo('user1', user)
.find().then(function(res) {
return res.map(function(r) {
return r.get('item');
});
});
})).then(function() {
return Array.prototype.slice.apply(arguments).reduce(function(arr, arr_) {
return arr.concat(arr_);
}, []);
});
}
// This is the original outer for() loop, now expressed as a .reduce(), which is
// a common pattern for performing a series of async tasks (ie what was the inner loop).
// Here, `userArray.reduce(...)` returns a promise of an array of
// the objects returned by `r.get('item')` above.
return userArray.reduce( function(p, user) {
return p.then(function(arr) {
return asyncProcessUser(user).then(function(arr_) {
return arr.concat(arr_);
});
});
}, Parse.Promise.as([]) );//† "returns a new promise that is resolved with a given value".
}
† : Documentation for Parse.Promise.as()
Without the comments, it's quite concise.
The concept is demonstrated here. Don't worry that the demo uses jQuery promises, it's the concept that matters.

Use this
function parseForUser(user) {
var promises = [];
for (var i2 = 0; i2 < test.length; i2++) {
var items = [];
var UserFavourite = Parse.Object.extend("UserFavourite");
var queryUserFav = new Parse.Query(UserFavourite);
queryUserFav.equalTo('item', test[i2].get('item'));
queryUserFav.equalTo('school', test[i2].get('school'));
queryUserFav.equalTo('user1', user)
var prom = queryUserFav.find().then(function(res) {
for (var i3 = 0; i3 < res.length; i3++){
var item = res[i3];
var itemName = item.get('item');
items.push(itemName);
console.log(items)
}
return items;
});
promises.push(prom);
}
return Parse.Promise.when(promises);
}
function parseUserArray(userArray) {
var returnPromise = new Parse.Promise(); // Do you have to call it via new?
//The documentation isn't clear.
var index = 0;
var doNext = function() {
if(index < userArray.length) {
var promise = parseForUser(userArray[index++]);
promise.done(doNext);
} else {
returnPromise.resolve();
}
}
doNext();
return returnPromise;
}
Just copy and paste

Related

Equivalent of set in ES6 to ES5

I have a set over which I am iterating in ES6. I am trying to convert it to its equivalent in ES5. My build is getting failed because of ES6. That's why I am converting it to ES5.
Here's my code in ES6
service.getDevices = function (date) {
var result = [];
var deviceList = devices[date.getTime()];
for (let item of deviceList) { // browser compatibility: support for ECMA6
result.push({"deviceName": item});
}
return result;
}
I am getting error because of 'let'. I tried using for (var item in deviceList), it does not display the charts.
I also tried this:
for(var i = 0; i < deviceList.length(); i++){
result.push({"deviceName" : deviceList[i]});
}
Even this is not working for set. can someone help and tell me how to iterate over a set in ES5 and if that is not possible, is there any equivalent way of doing it?
This is a basic set es5 class that I have used variations on over the years.
function Set(items) {
this._data = {};
this.addItems(items);
}
Set.prototype.addItem = function(value) {
this._data[value] = true;
return this;
}
Set.prototype.removeItem = function(value) {
delete this._data[value];
return this;
}
Set.prototype.addItems = function(values) {
for (var i = 0; i < values.length; i++) {
this.addItem(values[i]);
}
return this;
}
Set.prototype.removeItems = function(values) {
for (var i = 0; i < values.length; i++) {
this.removeItem(values[i]);
}
return this;
}
Set.prototype.contains = function(value) {
return !!this._data[value];
}
Set.prototype.reset = function() {
this._data = {};
return this;
}
Set.prototype.data = function() {
return Object.keys(this._data);
}
Set.prototype.each = function(callback) {
var data = this.data();
for (var i = 0; i < data.length; i++) {
callback(data[i]);
}
}
var set = new Set(['a', 'b', 'c']);
console.log(set.addItems(['a', 'd', 'e']).removeItems(['b', 'e']).data());
console.log(set.contains('a'));
console.log(set.contains('e'));
set.each(console.log)
Why not just iterate through the data and map the result with Array#map.
result = deviceList.map(function (item) {
return { deviceName: item };
});
I think your problem with your second for example is just that length is a property and not a function so you shouldn't add () to the end of it. A working version of this might look like this:
for(var i = 0; i < deviceList.length; i++){
result.push({"deviceName" : deviceList[i]});
}
This assumes (as #grabantot pointed out) that deviceList is an array, however, if it's a Set then you need to use the deviceList.size property.
However, there is a more compatible version of your first for loop which is the forEach() function (which is available on Array and Set), like this:
deviceList.forEach(function (item) {
result.push({"deviceName": item});
});

javascript/mongoose use async function in for loop have unexpected behavior?

I have an async function named populateFavoriteItem in for loop :
var result = [];
for (var i = 0; i <array.length ; i++) {
populateFavoriteItem(accountId,array[i],function(doc){
result.push(doc);
//mark 1
})
// if (i == array.length - 1) {
// console.log(result);
// callback(result);
// }
}
//mark 2
console.log(result);
callback(result);
It always run mark 2 first and then run to mark 1. Therefore, I get a null result array. Seems that result is not the populateFavoriteItem callback function.
Im trying to write if() condition in the for loop but get the same consequence, what should I do?
Use Node.js eventEmitter:
var result = [];
var obj;
var j = 0;
var myEventEmitter = new eventEmitter;
myEventEmitter.on('next',addResult);
function addResult(){
result.push(obj)
j++;
if (j == array.length) {
callback(result);
}
}
var populateFav = promiseify(populateFavoriteItem);
for (var i = 0; i <array.length ; i++) {
var ii = i;
populateFavoriteItem(accountId,array[ii],function(doc){
//result.push(doc);
obj = doc;
myEventEmitter.emit("next");
})
}

pushing multiple arrays into one is forming a 2 dimensional array

Following code is returning the length of allRows[] as 3, because it has 3 arrays in it. I am trying to build one final array allRows.
getRows() {
return this.element.all(by.css(".xyz")).getText();
}
getTotalRows() {
const allRows = [];
for (let i = 0; i < 3; i++) {
allRows.push(this.getRows());
this.scrollDown();
this.waitToLoad();
}
return allRows;
}
Actually getRows() is returning an array of promises. Following changes to my code has fixed the issue
getRows() {
return this.pinnedRows.getText();
}
getTotalRows() {
const defer = Promise.defer();
let allRows = [];
for (let i = 0; i < 3; i++) {
this.getRows().then((rows) => {
allRows = allRows.concat(rows);
this.scrollDown();
this.waitToLoad();
if (i === 2) {
defer.resolve(allRows);
}
});
}
return defer.promise;
}
Pushing adds one index, what you want is concat()

Cannot set property 'XY' of undefined

I have following code:
var favourites = JSON.parse(localStorage.getItem("favourites"));
Service.all().then(function (multiple) {
var array = [];
for (var i = 0; i < multiple.length; i++) {
for (var j = 0; j < favourites.length; j++) {
if (favourites[j].checked === true) {
if (multiple[i].Name === favourites[j].name) {
Service.getAllBySomething(multiple[i].Id).then(function (resources) {
var arrayOfSomething = [];
for (var k = 0; k < resources.length; k++) {
arrayOfSomething.push(resources[k].ResourceCategoryId);
}
arrayOfSomething = arrayOfSomething .filter(function (elem, pos, arr) {
return arr.indexOf(elem) == pos;
});
multiple[i].existingProperty= arrayOfSomething;
});
array.push(multiple[i]);
}
}
}
}
$scope.vendors = array;
});
My problem is that it says everytime 'Cannot set property existingProperty of undefined'. And I don't know why multiple[i] should be undefined at this line:
multiple[i].existingProperty= arrayOfSomething;
The property exists, I am sure. And it is defined, it is an empty array. And this empty array I want to replace with my made array in the loops.
Where is the fault? How I can fill the existingProperty with my array?
is Service.getAllBySomething asynchronous by any chance? Because in that case, by the time the callback function runs, i (captured in a closure only) has been moved to the end of the array.
Use an additional closure to capture the value of i at the time of dispatching the async call, like this:
Service.getAllBySomething(multiple[i].Id).then(function(i){
return function (resources) {
var arrayOfSomething = [];
for (var k = 0; k < resources.length; k++) {
arrayOfSomething.push(resources[k].ResourceCategoryId);
}
arrayOfSomething = arrayOfSomething .filter(function (elem, pos, arr) {
return arr.indexOf(elem) == pos;
});
multiple[i].existingProperty= arrayOfSomething;
};
}() );
Note the new function receiving i as a param and returning the function you previously used. I'm invoking immediately (an IIFE) to return the function to pass as callback. Inside that function the value of i will be the same as when you dispatch this async call, as it was copied when used as a parameter.

Asynchronous for loop with Javascript

I have this code that I need to convert to async loop since obviously this code for-loop will block the UI/Browser:
$wnd.mainbuff = [];
$wnd.update = setInterval(function(){
// fetches everything in the buffer (csv), including old data
var temp = $wnd.Recorder.audioData().toString();
var arr = temp.split(',');
// so, copy new elements only (must be async)
for (var i=$wnd.mainbuff.length; i < arr.length; i++) {
console.log(arr[i]);
$wnd.mainbuff[i] = arr[i];
}
}
,25)
Turn your loop into an equivalent recursive function. It then will be easy to use setTimeout when calling itself.
$wnd.mainbuff = [];
$wmd.curTimerId = null;
$wnd.update = function() {
var start = Date.now();
// fetches everything in the buffer (csv), including old data
// doesn't sound good ^^^^^^^^
var temp = $wnd.Recorder.audioData().toString();
var arr = temp.split(',');
// so, copy new elements only
function processOne(i, callback) {
if (i < arr.length) {
console.log(arr[i]);
$wnd.mainbuff[i] = arr[i];
setTimeout(function(){processOne(i+1, callback);}, 0);
} else
callback();
}
processOne($wnd.mainbuff.length, function() {
setTimeout($wnd.update, 25- (Date.now()-start));
});
}
Try this:
$wnd.mainbuff = [];
function async(arr, wnd, currentIndex) {
console.log(arr[currentIndex]);
wnd.mainbuff[currentIndexi] = arr[currentIndexi];
}
$wnd.update = setInterval(function(){
// fetches everything in the buffer (csv), including old data
var temp = $wnd.Recorder.audioData().toString();
var arr = temp.split(',');
// so, copy new elements only (must be async)
for (var i=$wnd.mainbuff.length; i < arr.length; i++) {
async(arr, $wnd, i);
}
}
,25)

Categories