Issue structuring a promise in a loop - javascript

I am struggling with getting this resolved, as I am new to Promises.
I need to first read both the Summative and Formative from Firebase before I can determine the StudentPlacement
The way the code below, provides null as the StudentPlacement snapshot.val(), as it is not waiting for the x and y values.
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 xIndex = 0;
var yIndex = 0;
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());
});
}
}
Can anyone please help me structure the trigger!?

You're waiting for both functions to finish before executing the next bit of code. Look into Promise.all.
for (let i = 1; i <= section; i++) {
const xIndexRef = admin.database().ref('TestScores').child(data.key).child('Summative').child(i).once("value");
const yIndexRef = admin.database().ref('TestScores').child(data.key).child('Formative').child(i).once("value");
Promise.all([xIndexRef, yIndexRef])
.then(results => {
const xSnapshot = results[0];
const ySnapshot = results[1];
return admin.database().ref('StudentPlacement').child(data.key).child(xSnapshot.val() + ":" + ySnapshot.val()).once("value");
})
.then(snapshot => {
console.log("Student Placement is: ", snapshot.val());
});
}
Promise.all waits for both xIndexRef and yIndexRef to complete their execution.
Once executed the results are returned into a thenable object.
You can access the results and complete your execution.

Related

Async await with Fetch

my fetch works perfectly with .then, but i want to step it up a notch by using async and await. It should wait for all 5 API calls, and then place the answer, instead, it shows answer on every API call
async function getPhotosFromAPI() {
for (let i = 1; i <= 5; i++) {
let albums = await fetch(
`https://jsonplaceholder.typicode.com/photos/?albumId=${i}`
);
let result = await albums.json();
let res = `<div class="album${i}"></div>`;
document.querySelector(".display-images").innerHTML += res;
for (let j = 1; j <= 5; j++) {
document.querySelector(
`.album${i}`
).innerHTML += `<img src="${result[j].url}"/>`;
}
}
console.log(result);
}
async function showPhotos() {
await getPhotosFromAPI();
document.getElementById("#loader").style.display = "none";
}
showPhotos();
document.getElementById("img").style.display = "block";
for (let i = 1; i <= 5; i++) {
fetch(`https://jsonplaceholder.typicode.com/photos/?albumId=${i}`)
.then((response) => response.json())
.then((json) => {
document.getElementById("img").style.display = "none";
const display = document.querySelector(".display-images");
const albumNo = document.querySelector(".album-no");
// document.getElementById('img').style.display = "block";
// document.getElementById('img').style.display = "none";]
display.innerHTML += `<div class="album-${i}>`;
for (let z = 1; z <= 5; z++) {
display.innerHTML += `<img id="img" alt="pic-from-album${json[i].albumId}" src="${json[z].url}"/>`;
}
display.innerHTML += `<div>`;
});
}
You should use a concurrent way of fetching like Promise.all to avoid round-trips
async function getPhotosFromAPI() {
let albums = await Promise.all(
Array(5).fill().map((elem, index) =>
fetch(`https://jsonplaceholder.typicode.com/photos/?albumId=${index+1}`)
)
)
let results = await Promise.all(
albums.map(album => album.json())
)
return results
}
//Display
You're asking for the code to wait until each fetch finishes by using await on fetch's return value (then again on the return value of json) in your loop. So it will do just that: wait until that request is complete before moving on to the next loop iteration.
If you don't want to do that, you need to start each fetch one after another and then wait for them all to complete. I'd probably break out the work for just one of them into a function, then call it five times, building an array of the promises it returns, then await Promise.all(/*...*/) those promises, something along these lines:
document.getElementById("img").style.display = "block";
// Fetch one item
const fetchOne = async (i) => {
const response = await fetch(`https://jsonplaceholder.typicode.com/photos/?albumId=${i}`);
if (!response.ok) {
throw new Error(`HTTP error ${response.status}`);
}
const data = await response.json();
document.getElementById("img").style.display = "none";
const display = document.querySelector(".display-images");
const albumNo = document.querySelector(".album-no");
// document.getElementById('img').style.display = "block";
// document.getElementById('img').style.display = "none";]
display.innerHTML += `<div class="album-${i}>`;
for (let z = 1; z <= 5; z++) {
display.innerHTML += `<img id="img" alt="pic-from-album${data[i].albumId}" src="${data[z].url}"/>`;
}
display.innerHTML += `<div>`;
};
// ...
await Promise.all(Array.from({length: 5}, (_, i) => fetchOne(i + 1)));
// All done
(I took the version with .then as my starting point for the above, since the two versions in your question were so different and you said the one with .then worked... Also note that I renamed the variable json to data, since it doesn't contain JSON [it's not a string], it contains the result of parsing JSON.)

Values of a promise in JS within for loop

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"
});

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);
}

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);
});
}
});

Categories