the following code block is returning an empty array but if I use for loop instead it works fine
let allMenus = [];
foodMenu = foodMenu.map(async menu => allMenus.push(await handleMenu(menu, DBName)))
console.log(foodMenu); // returns empty array
this return the data perfectly but I want to use map
let allMenus = [];
for (const menu in foodMenu) {
allMenus.push(await handleMenu(foodMenu[menu], DBName)); // this returns the data
}
A few things:
Firstly, Array#map expects a return value because it generates a new array. While your code works, it is not the intent of the method.
Using await in an array enumerator callback (Array#map, in yours case) will defer the execution, but it does not pause between callbacks. That means the code will be run, but it will not be resolved in sequence in the way you are expecting.
Do this:
let foodMenu = foodMenu.map(async menu => {
const newMenuItem = await handleMenu(menu, DBName)
console.log(newMenuItem)
allMenus.push(menu)
})
You will find that your return value, the empty array, will be printed first, and then your new menus printed afterwards. It is out of order
To resolve this, you either need to
Make it a loop, where await that will pause in the way you expect or
Map the promises into an array and wait for them all to finish, using Promise.all
let foodMenu = await Promise.all(foodMenu.map(menu => handleMenu(menu, DBName)))
Related
I am ultimately pushing unresolved promises to be values of an object and then awaiting those to recall later:
let names = ["aaron", "bob", "john", "aaron"];
let allNames = {};
// just setting up my object with names as the key
names.forEach(name => {
if (!allNames[name]) {
allNames[name] = null;
}
});
//iterating through and running async requests in parallel and storing the unresolved promises as the object value
let allDataForNamedPerson;
for (const [x] of names) {
if (!allNames[name]) {
allDataForNamesPerson = this.getNameData(name)// async api call
allNames[name] = allDataForNamesPerson // storing the promise
}
}
then I await until all these have resolves;
await Promise.all([...Object.values(this.allNames)]);
my problem is that when I go to looking up one of the resolved values from the object, for example:
allNames["john"] , the value in console an Object wrapped in a promise:
promise {
salary: 41992,
position: clerk
}
so I cannot access it via allNames["john"].salary -- that comes back as undefined. What I doing wrong here?
await doesn't replace promises with their values. If you want to work with the values, you have to grab them yourself:
const resolvedValues = await Promise.all([...Object.values(this.allNames)]);
and then resolvedValues[someIndex] gives you unwrapped data.
Using bare await only guarantees that a given promise is settled in the code after the await line but the values are still wrapped.
I have this code that is checking if my userContacts ids exist in another collection, and I'm returning all the matches.
async function fetchCommonNumbers() {
var commonNumbers = [];
let contactsReference = admin.firestore().collection("user_contacts").doc("iNaYVsDCg3PWsDu67h75xZ9v2vh1").collection("contacts");
const dbContactReference = admin.firestore().collection('db_contacts_meta');
userContacts = await contactsReference.get();
userContacts = userContacts.docs;
await Promise.all(
userContacts.map(userContact => {
const DocumentID = userContact.ref.id;
//Check if Document exists
return dbContactReference.doc(DocumentID).get().then(dbContact => {
if (dbContact.exists) {
console.log(DocumentID);
commonNumbers.push(dbContact.data());
}
});
}));
return Promise.resolve(commonNumbers);
}
I need to only return X matches and not all since later I'll be having million of records and I want to reduce processing time.
How can I make the Promise.all to stop when commonNumbers has X items in it?
Currently there is not implementation of cancelable promises (more info can be found here enter link description here),
If you want, you can define your own "cancelable promise" wrapping a normal promise.
Reduce the processing time without "stopping" promises
You can't really make the promises stop. But since you're looking to reduce the number of database calls, what you can do is to selectively resolve your promises.
For example you can include a conditional statement in your map function. Like this
if commonNumbers.length < maxLength then return me a Promise containing the database call
Else, just resolve a random value (like false in my example)
Your promises will still be there, but you will have limited the number of DB calls to the necessary. It will look something like this
const arr = [1, 2, 3, 4, 5, 6];
const buffer = [];
const maxLenBuffer = 3;
const p = Promise.all(
arr.map(n => {
if (buffer.length < maxLenBuffer) {
buffer.push(n);
return Promise.resolve(n);
} else {
// There's still a promise to be resolved, but it's not a HTTP call
// This gives you the gain of performance you're looking for
return Promise.resolve(false);
}
})
);
p.then(() => console.log(buffer));
Note
While this can reduce your database calls, the actual number of calls can be a little higher than your maximum specified. This is due to the asynchronous nature of the calls
Instead of breaking the promise in between, I would suggest you use the limit method do firestore.
You can query only for X number of records and this X can be either hardcoded or can come from user. Something like:
documentRef.orderBy("name").limit(3).get()
I have an array like this
let result = [{id:1,name:'test',dealValue:'ds',dealType:2},{id:2,name:'test1',dealValue:'ds',dealType:4}];
I am looping the above array to call another function which is also a promise after each iteration I need to add the value as a new item in the current array.
let temp = [];
result.forEach((element, index) => {
//build the promise
temp.push(getAmount(element.dealValue, element.dealType));
});
//execute array of promise
let r = await Promise.all(temp);
//add new key value pair
result.forEach((element, index) => {
element.IDGfee = r[index];
});
This works fine, however, I am running two foreach loops to achieve my desired result ,is there any better way to do this??
You could use .map instead, and assign back to element inside a .then chained onto the getAmount call:
await Promise.all(
result.map((element) => (
getAmount(element.dealValue, element.dealType)
.then((result) => {
element.IDGfee = result;
})
))
);
(Though, as comment notes, this will not wait for every response to come back before assigning to each element - your current code will throw before assigning if any request throws an error, whereas this may well throw after some properties have been assigned to.)
I have a global variable called 'data' that is being modified inside a forEach loop. However, since the loop is asynchronous, the code does not wait until data is populated before it continues with the code. This is using the JSZip library.
let data = [];
await zip.folder("folderName").forEach(async function (relativePath, file) {
let json = await zip.file(file.name).async("text");
data.push(json);
console.log(data.length); // prints increasing numbers
});
console.log(data.length); //prints 0
// need to do something with data but it is empty
How do I wait for the data array to be populated before I continue with the code?
forEach() has no return value so it cannot be awaited. You'll have to populate an array of promises from each ZipObject#async() and await that array using Promise.all() to get the results:
const promises = [];
zip.folder("folderName").forEach(function (relativePath, file) {
promises.push(zip.file(file.name).async("text"));
});
Promise.all(promises).then(function (data) {
// do something with data
});
According to JSZip documentation, there's no way to convert forEach(callback) to an array of Promises. So the only way I came up with was to get the number of files and use a counter.
const myFolder = zip.folder("folderName");
const numberOfCallbacks = Object.keys(myFolder.files).length - 1;
let counter = 0;
myFolder.forEach((relativePath, file) => {
// your code. you'd better create a Promise here and add it to an array of promises.
counter++;
if (counter === numberOfCallbacks) {
// everything is done. If you created Promise above, here you can use Promise.all()
}
});
I tested the above code and it worked. Let me know if there's a problem.
I have the below code to access movie sessions from a cinema site. I am looping using a while loop to fetch movie sessions.
And I intend to add the sessions within the loop to array sessionResults which is declared outside the while loop.
R. refers to the Ramda library
let page // passed as an argument to the outer function
let sessionResults = [];
while (currentCinemaIndex < cinemaList.length) {
await page.goto("www.fakeurl.com");
const _movies = await movies({ page });
//Get the sessions for each #_movies
const _movieSessions = await _movies.map(
async (_movie, index) => {
//sessions() returns an array of objects
const res = (await sessions({ page: page }, index + 1)).map(session => {
return Object.assign({}, _movie, session);
});
return res;
},
{ page }
);
//!!! AREA OF CONCERN
console.log(_movieSessions); // array of promises
Promise.all(_movieSessions).then(p => {
sessionResults = R.concat(R.flatten(p), sessionResults);
// console.log(sessionResults); // concatenated array
});
console.log(sessionResults); // []
//while loop logic
currentCinemaIndex = //increment currentCinemaIndex
limit =// set new limit
If you look at //!!! AREA OF CONCERN I have documented the value of sessionResults at different places.
Could you please advise why the value of sessionResults is not carried through outside Promise.all()?
You don't get the updated value of sessionResults because by the time the code executes until the console.log(sessionResults) after Promise.all(...), the promise has not resolved yet.
Therefore, the sessionResults returned by console.log is not yet updated.
What you can do instead is use await like below:
p = await Promise.all(_movieSesions);
sessionResults = R.concat(R.flatten(p), sessionResults);
console.log(sessionResults);
Please note that if you would use await like above, you need to do it inside an async function scope and not the global scope (because it is not async).
await Promise.all() worked based on comment from #CertainPerformance
Revised code looks like
sessionResults = await Promise.all(_movieSessions).then(p => R.flatten(p));
console.log(sessionResults); // concatenated array