Values of a promise in JS within for loop - javascript

I am lost in the promised land and could really use some guidance. I have exhausted searching numerous SO questions (2-3 hours of reading solutions + docs) related to this seemingly common issue and feel I just am not getting it.
Overview
Below I have code that takes in an Object type (resources), grabs a few values from this Object and then calculates distance and duration from the GoogleMaps Distance Matrix. The results of the function googleRequest() are a promise containing two values (distance and duration).
I would like to get these two values back within the for loop, execute pushToRows(), and then return an array called final_rows.
Problem
final_rows shows UNDEFINED for the duration and distance keys within each row. I speculate this is occurring because I am attempting to access the values in dist_dur inappropriately. I would appreciate any help on resolving this issue. Thanks.
Code
final_rows = []
function getDistTime(resources){
for (var i = 0; i < resources.data.length; i++) {
var origin1 = $("#citystate").val();
var destinationA = resources.data[i]['DEMOBILIZATION CITY'] + ',' + resources.data[i]['DEMOBILIZATION STATE'];
var dist_time_data = googleRequest(origin1, destinationA).then((values) => {
return values
})
pushToRows(resources.data[i], dist_time_data)
}
// console.log(final_rows)
}
function pushToRows(resources, dist_dur){
resources["DISTANCE_MI"] = dist_dur[0];
resources["ACTUAL_DUR_HR"] = dist_dur[1];
resources["FINANCE_DUR_HR"] = (dist_dur[0] / 45.0).toFixed(2)
final_rows.push(resources)
}

So, what you would need to do is just store promises in an array in the for loop and then wait for these promises to resolve using Promise.all but this would parallelize your requests to google distance api.
function getDistTime(resources){
const promiseArr = [];
for (var i = 0; i < resources.data.length; i++) {
var origin1 = $("#citystate").val();
var destinationA = resources.data[i]['DEMOBILIZATION CITY'] + ',' + resources.data[i]['DEMOBILIZATION STATE'];
promiseArr.push(googleRequest(origin1, destinationA));
}
// Not sure how would you use the data pushed in rows but since you are not waiting for promises to be resolved, data would be updated later on
return Promise.all(promiseArr)
.then((resultsArr) => {
resultsArr.forEach((result, i) => pushToRows(resources.data[i], result));
})
}
function pushToRows(resources, dist_dur){
resources["DISTANCE_MI"] = dist_dur[0];
resources["ACTUAL_DUR_HR"] = dist_dur[1];
resources["FINANCE_DUR_HR"] = (dist_dur[0] / 45.0).toFixed(2)
final_rows.push(resources)
}
I would recommend to use async-await which are syntactic sugar to promises but make your code easy to understand and remove the complications that come with promise chaining.

If you move your pushToRows() inside where you return values, you will have access to that data.
googleRequest(origin1, destinationA).then((values) => {
pushToRows(resources.data[i], values);
});
Until that promise resolves, dist_time_data would be undefined
You could also convert to Promise.all() which takes an array of promises and resolves when all of the promises are complete:
function getDistTime(resources){
const promises = [];
for (var i = 0; i < resources.data.length; i++) {
var origin1 = $("#citystate").val();
var destinationA = resources.data[i]['DEMOBILIZATION CITY'] + ',' + resources.data[i]['DEMOBILIZATION STATE'];
promises.push(googleRequest(origin1, destinationA));
}
return Promise.all(promises).then((results) => {
return results.map((result, i) => {
return {
...resources.data[i],
DISTANCE_MI: result[0],
ACTUAL_DUR_HR: result[1],
FINANCE_DUR_HR: (result[0] / 45.0).toFixed(2)
};
});
});
}
getDistTime(resources).then(result => {
//result is now "final_rows"
});

Related

loop 100+ getJSON calls and call a another function when completely done

I need to read a grid and take that data and call a $getJSON url. The grid could have over 100 lines of data. The getJSON returns a list of comma separated values that I add to an array. Once the loop is finished I take the array and process it for the duplicates. I need to use the duplicates in another process. I know that I can't determine the order of the data that is coming back but I need to know that all of the calls have been make.
for (let i = 0; i < rowscount; i++){
$.getJSON(
"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&retmode=json&retmax=500&term=" +
terms,
function (data) {
var pmids = data.esearchresult.idlist;
var pmidlist = pmids.join();
pmid_List.push(pmidlist);
if (i == rowscount - 1) {
// call the related function
}
});
}
I can't figure out how to be sure that the process has finished. The call to the related function has been done early at times.
Well if we keep track of how many have completed we can fire off the code when the last one is done.
let complete = 0;
for (let i = 0; i < rowscount; i++){
$.getJSON(
"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&retmode=json&retmax=500&term=" +
terms,
function (data) {
var pmids = data.esearchresult.idlist;
var pmidlist = pmids.join();
pmid_List.push(pmidlist);
complete += 1;
if (complete == rowscount) {
// call the related function
}
});
}
I'd use fetch and Promise.all
const link = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&retmode=json&retmax=500&term=";
Promise.all(Array.from({
length: 3
}, () => fetch(link + 'foo').then(e => e.json()))).then(e => {
//called when all requests are done
console.log(e);
})
Try this
function getJson(url, i) {
return $.getJSON(url, function (data) {
//var pmids = data.esearchresult.idlist;
//var pmidlist = pmids.join();
//pmid_List.push(pmidlist);
console.log('completed', i)
return data;
});
}
function run() {
let promises = []
for (let i = 0; i < rowscount; i++) {
const terms = 'foot';
const url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&retmode=json&retmax=500&term=" + terms;
promises.push(getJson(url, i));
}
return promises;
}
Promise.all(run()).then(() => console.log('All are completed'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Promise.all() not always resolved - edited to add the code for both functions

I'm using the following code to update the state but the Promise.all() array isn't always resolved before the final function is called. Sometimes, the entire array is populated but it usually isn't.
I'm logging the Promise.all() array to the console just so I can see what's happening. What is really peculiar is the state is always updated properly but calling the individual elements in the array often returns "undefined". I need to use the individual elements to create a new array for displaying later which is why I'm accessing them.
Please help me either figure out how to access the array when it's finished updating or a better way to process the entire thing.
class X {
componentDidMount() {
const numTiles = 3;
for (let i = 0; i < numTiles; i++) {
Promise.all([
this.fetchSong(), // returns JSON from SQLite DB
this.fetchArtists(), // returns JSON from SQLite DB
])
.then(values => {
this.testing(values);
});
}
}
testing(arr) {
console.log("arr: ", arr);
console.log("arr[0]: ", arr[0]);
console.log("arr[0].id: ", arr[0].id);
console.log("arr[0].name: ", arr[0].name);
console.log("arr[0].artist: ", arr[0].artist);
console.log("arr[1]: ", arr[1]);
console.log("arr[1][0]: ", arr[1][0]);
console.log("arr[1][1]: ", arr[1][1]);
console.log("arr[1][0].artist: ", arr[1][0].artist);
console.log("arr[1][1].artist: ", arr[1][1].artist);
}
}
Edit: code for fetchSong() and fetchArtists() added.
fetchSong() {
let id = Math.floor(Math.random() * 2000) + 1; // get random number for id
return new Promise((resolve, reject) => {
Bingo.getSong(id).then(song => {
resolve(song);
});
});
}
fetchArtists() {
return new Promise((resolve, reject) => {
let arr = [];
for (let j = 0; j < 2; j++) {
let id = Math.floor(Math.random() * 10) + 1;
Bingo.getArtist(id).then(artist => {
arr.push(artist);
});
resolve(arr);
};
});
}
The console screenshot below shows the Promise.all() array has been populated but the array elements are still missing.
The problem is in the implementation of fetchArtists, which resolved before any of the single-artist-fetching promises were resolved.
I've also simplified fetchSong, but the gist of fetchArtists now is you store the promises that will resolve to single artists, then wait for all of them to resolve. There's no reason to add a .then() there, since as you know, Promise.all() will resolve with an array of resolved values anyhow.
fetchSong() {
const id = Math.floor(Math.random() * 2000) + 1;
return Bingo.getSong(id);
}
fetchArtists() {
const fetchPromises = [];
for (let j = 0; j < 2; j++) {
const id = Math.floor(Math.random() * 10) + 1;
fetchPromises.push(Bingo.getArtist(id));
}
return Promise.all(fetchPromises);
}

Add Sum of all values from locators of a web table, method results values of only first two locators

I want to add all the values of locators from a web table but it is adding only first two values. here is my method declaration below.
exports.GetMonthsFWSeasonSTDPreMkdValues = () => {
var total_value;
for(var month_index = 9; month_index <= 18 ; month_index++){
const elm_xpath = utils.GetXpathForSubCategory(chosen_season_index, month_index);
return browser.element(by.xpath(elm_xpath)).getText().then(function(response_total_months_index_values){
total_value += response_total_months_index_values;
console.log('total value' ,total_value);
});
}
};
The root case is you use return inside For loop, thus the loop only be iterated one time.
And another hiddened code issue is javascript closure issue, the For loop execute as Sync, but the getText() inside loop execute as Async.
If you remove keyword return, the browser.element(by.xpath(elm_xpath)).getText() will repeat use the elm_xpath of month_index=18
exports.GetMonthsFWSeasonSTDPreMkdValues = () => {
var promises = [];
for(var month_index = 9; month_index <= 18 ; month_index++){
const elm_xpath = utils.GetXpathForSubCategory(chosen_season_index, month_index);
promises.push(element(by.xpath(elm_xpath)).getText());
}
return Promise.all(promises).then(function(data){
return data.reduce(function(accumulator, currentValue){
return accumulator + currentValue * 1;
}, 0);
});
};
//Remind: this function return a promise.

Cloud Functions for Firebase - Looping with Promises

I'm in the process of trying to create a trigger that would add up test scores and then calculate the students placement based on previous test results.
I am attempting to utilize Promises within a FOR loop as seen below:
exports.boxScoresUpdate = functions.database.ref('/Tests/{id}/TestScores').onWrite(event => {
let testScr = 0;
for (let i = 1; i <= section; i++) {
//
testScr += parseInt(nValue[i]);
var index;
admin.database().ref('TestScores').child(data.key).child('Summative').child(i).once("value").then(x => {
xIndex = x.val();
admin.database().ref('TestScores').child(data.key).child('Formative').child(i).once("value")
}).then(y => {
yIndex = y.val();
admin.database().ref('StudentPlacement').child(data.key).child(xIndex + ":" + yIndex).once("value", snapshot => {
// SnapShot
console.log("Student Placement is: ", snapshot.val());
});
}).catch(reason => {
// Handle Error
console.log(reason);
});
}
}
Which I was told would not work as seen in this post.
"Once a promise is resolved or rejected, it forever retains that state and can't be used again. To repeat the work, I think you'd have to construct another chain of new promises representing the second iteration of work."
I have been attempting to restructure my trigger but I can not figure it out, how would I construct the new chain of promises to achieve my desired result?! Has anyone ever encountered and overcome this issue?
The behavior I am hoping to achieve is make the trigger iterate for four (4) iterations section is equal to 4.
I needed to utilize promises else the iteration would not complete correctly, specifically testScr += parseInt(nValue[i]); and the lookup for Summative and Formative.
But as stated, using Promises is working perfectly except it only iterates for the first instance, and not for when the i = 2 or 3 or 4
This approach is not that clean but might help you.
exports.boxScoresUpdate = functions.database.ref('/Tests/{id}/TestScores').onWrite(event => {
let testScr = 0;
for (let i = 1; i <= section; i++) {
//
testScr += parseInt(nValue[i]);
var index;
admin.database().ref('TestScores').child(data.key).child('Summative').child(i).once("value").then(x => {
xIndex = x.val();
return { xIndex, index: i };
}).then(({ xIndex, index}) => {
admin.database().ref('TestScores').child(data.key).child('Formative').child(index).once("value").then(y => {
yIndex = y.val();
return { yIndex, xIndex };
}).then(({ yIndex, xIndex}) => {
admin.database().ref('StudentPlacement').child(data.key).child(xIndex + ":" + yIndex).once("value", snapshot => {
console.log("Student Placement is: ", snapshot.val());
});
});
}).catch(reason => {
console.log(reason);
});
}
});

Javascript.Run Multi promises Synchronously

I want to request a website for 40 times.
I want this to be synchronously, like 10 requests 4 times.
This is My code for 1 request - 40 times:
'use strict';
var request = require('request');
var co = require('co');
function callUrl(url) {
return new Promise((resolve, reject) => {
request.get(url, (e, r, b) => {
if (e) reject(e)
else
resolve(`Response from ${url}`);
});
})
}
co(function*() {
for (var i = 1; i < 41; i++) {
let rs = yield callUrl('https://www.google.com/?q=' + i);
// let rs = yield makeUrls(10,i);
console.log(rs);
}
});
I can make an array of promises, but I can't figure it out how to change the value of q to be different.
You don't want to run them synchronously - you want to synchronize them - those are different.
You'd use an array of promises together with Promise#all. When you create a promise the action is already being executed - only yield synchronizes things.
You can make 10 requests at once like so:
co(function*() {
for (var i = 1; i < 41;) {
var promises = [];
for(var lim = i + 10; i < Math.max(lim, 41); i++) {
promises.push(callUrl('https://www.google.com/?q=' + i));
}
let rs = yield Promise.all(promises); // wait for everything
console.log(rs); // an array of 10 results
});
Note that in addition to that, your code is still not very efficient - what happens if 9 out of 10 requests are really fast and one takes a minute? You'll only have one outgoing requests. You can use a library like bluebird which has a more efficient Promise.map method with a concurrency parameter.
this might work w/o using generators
const urls = [/*array of urls*/];
const initialPromise = request[urls[0]];
let promise = initialPromise;
for(let i= 1; i<40;i++){
let thenFunction = response => {
//do something with the response
return request(urls[i])
}
promise = promise.then(thenFunction)
}
the idea behind this is to build the chain of promises so the next one will waif for the previous one to finish

Categories