Is it possible to group independent async functions under a single await? - javascript

Background
I am writing some asynchronous code in express. In one of my end points there I need to retrieve some data from firebase for 2 seperate things.
one posts some data
the other retrieves some data to be used in a calculation and another post.
These 2 steps are not dependent on one another but obviously the end result that should be returned is (just a success message to verify that everything was posted correctly).
Example code
await postData(request);
const data = await retrieveUnrelatedData(request);
const result = calculation(data);
await postCalculatedData(result);
In the code above postData will be holding up the other steps in the process even though the other steps (retrieveUnrelatedData & postCalculatedData) do not require the awaited result of postData.
Question
Is there a more efficient way to get the retrieveUnrelatedData to fire before the full postData promise is returned?

Yes, of course! The thing you need to know is that async/await are using Promises as their underlying technology. Bearing that in mind, here's how you do it:
const myWorkload = request => Promise.all([
postData(request),
calculateData(request)
])
const calculateData = async request => {
const data = await retrieveUnrelatedData(request);
const result = calculation(data);
return await postCalculatedData(result);
}
// Not asked for, but if you had a parent handler calling these it would look like:
const mainHandler = async (req, res) => {
const [postStatus, calculatedData] = await myWorkload(req)
// respond back with whatever?
}

Related

How to get info from a JSON?

I'm new around here and I'm studying JS! In particular JSON! However, I have come across an exercise that I cannot solve, also because I do not understand what I am doing wrong. I need to extract the information about the planets from the StarWars API. So I do the classic fetch and as a result I get the generic information about the planet in the form of a JSON.
However, I have to extract the planet name and I get stuck, because when I check the PlanetsData variable, it gives me undefined. Ergo the cycle I wrote to extract the names of the planets doesn't work for some reason.
So, my question is:
Why do I get "undefined" for the PlanetsData variable? .. Shouldn't I get the JSON, which displays correctly in the console?
Did I write the cycle correctly?
Thanks to who will answer me!
This is my code:
async function getPlanetsData() {
const planetsData = await fetch ("https://swapi.dev/api/planets").then(data => {
return data.json()}).then(planets => {console.log(planets.results)}) // ---> Here i receive the JSON data
for (let key in planetsData) {
const someInfo = planetsData.results[key].name
console.log(JSON.stringify(someInfo)) } // ---> I don't understand why, but I don't get anything here. There is no response in the console, as if the call did not exist
}
getPlanetsData()
You can write the same function in a different and clearer way,
check the comments to understand the code!
async function getPlanetsData() {
// 1. Fetch and wait for the response
const response = await fetch ("https://swapi.dev/api/planets");
// 2. Handle the response, in that case we return a JSON
// the variable planetsData now have the whole response
const planetsData = await response.json();
// console.log(planetsData); // this will print the whole object
// 3. Return the actual data to the callback
return planetsData;
}
// Function usage
// 4. Call "getPlantesData" function, when it completes we can call ".then()" handler with the "planetsData" that contains your information
getPlanetsData().then(planetsData => {
// 5. Do whatever you want with your JSON object
// in that case I choose to print every planet name
var results = planetsData.results; // result array of the object
results.forEach(result => console.log(result.name));
});
It seems that you have the same issue as : read and save file content into a global variable
Tell us if it does solve your issue or not.
(UPDATE)
To answer explicitly to your questions.
First question:
To get value into variable planetsData you can do this:
async function getPlanetsData() {
const response = await fetch ("https://swapi.dev/api/planets")
const planetsData = await response.json()
for (let key in planetsData) {
const someInfo = planetsData.results[key].name
console.log(JSON.stringify(someInfo))
}
}
getPlanetsData()
Second question:
You didn't write the cycle correctly.
To resolve promises it is preferable to choose between using await and.

How to await asynchronous map function?

let me quickly get to the point. I have a function which fetches some user ids. It works very well.
const allIds = await fetchAllIds(someUrl);
Now the problem comes when I want to do something different. I'd like to loop over those ids and do some async await stuff with them. That is, to mainly fetch some data for each one with axios and modify them accordingly.
allIds.map(async (id) => {
// 1. Fetch some data (try, catch and await)
// 2. Modify the id based on that data
// 3. Return the id, namely replace the old one
});
At the end of my code, I simply return allIds. The problem is that it returns them without waiting for the map function to execute completely. I tried different methods and none of it seems to be working. Could you please help me to make it work or perhaps suggest some other possible solutions? Thanks in advance!
You basically have two problems:
You are ignoring the return value of the map
The map will return an array of Promises and you aren't awaiting them all
So:
const promises = allIds.map(...);
const replacement_ids = await Promise.all(promises);
return replacement_ids;
Use this instead.
const newList = await Promise.all(allIds.map(id=>new Promise(async(res)=>{
// do your async here and get result;
res(result);
})));

Using JavaScript Promise All - is this syntax valid?

I have two tables with users, where each id for one user is same in both tables (don't ask why I have two user tables).
At some point, I need to filter users from table 1, and if certain condition is true, I store a promise (deleting request) for each user into (let's call it) tableOnePromises. I do the same for table 2.
In order to empty table 2, I MUST first empty table one due to some requirements.
this is what I did:
let tableOnePromises = [];
let tableTwoPromises = [];
tableOne.forEach(item => {
if(item.deactivated) {
const tableOneDeleted = supabase
.from("table-one")
.delete()
.match({id: item.id});
tableOnePromises.push(tableOneDeleted);
const tableTwoDeleted = supabase
.from("table-two")
.delete()
.match({id: item.id});
tableOnePromises.push(tableTwoDeleted);
}
});
await Promise.all(tableOnePromises).then(() => {
return Promise.all(tableTwoPromises)
}).catch(err => console.log(err));
Assuming the code using await is inside an async function (or at the top level of a module), the syntax is correct, but it's probably not what I'd use (in general, avoid mixing async/await with explicit callbacks via .then and .catch), and separately it's probably not working quite as you expect (this is borne out by your saying that your code was failing to delete from table-two).
For any particular id value, your code starts deleting from table-one and then immediately starts deleting from table-two without waiting for the deletion in table-one to complete:
// STARTS the deletion but doesn't wait for it to finish
const tableOneDeleted = supabase
.from("table-one")
.delete()
.match({id: item.id});
// ...
// Starts deleting from `table-two`, even though the item may still be in `table-one`
const tableTwoDeleted = supabase
.from("table-two")
.delete()
.match({id: item.id});
Remember that a promise is just a way of observing an asynchronous process; by the time you have the promise, the process it's observing is already underway.¹ So even though you don't wait for the table-two promises until later, you start the table-two deletions immediately.
...I MUST first empty table one due to some requirements...
If by "empty" you mean just that you have to ensure you've done the delete for a particular id on table-one before doing it on table-two, you need to wait for the table-one deletion to be completed before starting the table-two deletion. I'd put that in a function:
async function deleteItem(id) {
await supabase
.from("table-one")
.delete()
.match({id});
await supabase
.from("table-two")
.delete()
.match({id});
}
Then the code becomes:
const promises = [];
for (const {deactivated, id} of tableOne) {
if (deactivated) {
promises.push(deleteItem(id));
}
}
await Promise.all(promises); // With the `try`/`catch` if desired
...or if it's okay to make two passes through the array:
await Promise.all( // With the `try`/`catch` if desired
tableOne.filter(({deactivated}) => deactivated)
.map(({id}) => deleteItem(id))
);
¹ "...by the time you have the promise, the process it's observing is already underway." That's the normal case. There is unfortunately a popular document DB library that doesn't start its work on something until/unless you call then on the promise for it. But that's an exception, and an anti-pattern.

How to return a result properly from async/await function?

I have worked with async/await functions for a few months now and I have gotten all my code to work, however, I don't understand it completely and could use some words of wisdom.
In my code below I have an async function, my question is... why does it return a promise instead of the value I expect? And more importantly, what is the proper way to get the value I want?
getName = async () => {
const request = await fetch(apiUrl)
const data = await request.json()
return data.response.name
}
I call the function later
const name = getName()
But name is then a promise instead of the name.
I have looked this up many times and it never sticks. I am hoping using my own example if someone can explain it to me so I understand completely it will finally stick with me.
EDIT - In response to #Paulpro I have updated my question to below...
Assuming the async function is okay, the question is, how do I store the value.
const name = getName()
'name' is now a promise, so I normally do something like this...
getName().then(name => setName(name))
but how do I make const name === the returned name?
how do I make const name === the returned name?
By using const name = await getName().
Which means you have to use it inside an async function.
So your approach using:
getName().then(name => setName(name))
is most likely the best way to do it, but it really depends on the the rest of your code.
I mean you could do it like this:
let name;
getName = async () => {
const request = await fetch(apiUrl)
const data = await request.json()
name = data.response.name;
}
When (and if) both of your async requests finish, your name variable will have the result. But there is no guarantee on the timing of that. Depending on the app, it might be fine if you initialize your variable like this:
let name = 'Loading...';

Wait for actions to finish before executing again in puppeteer

I have a puppeteer script that inputs some text into a field, submits the query, and processes the results.
Currently, the script only processes 1 search term at a time, but I need it to be able to process an array of items consecutively.
I figured I would just put the code in a loop (see code below), however, it just types in all the items from the array at once into the field and doesn't execute the code block for each search term:
for (const search of searchTerms) {
await Promise.all([
page.type('input[name="q"]', 'in:spam ' + search + String.fromCharCode(13)),
page.waitForNavigation({
waitUntil: 'networkidle2'
})
]);
const count = await page.evaluate((sel) => {
return document.querySelectorAll(sel)[1].querySelectorAll('tr').length;
}, 'table[id^=":"]');
if (count > 0) {
const more = await page.$x('//span[contains(#class, "asa") and contains(#class, "bjy")]');
await more[1].click();
await page.waitFor(1250);
const markRead = await page.$x('//div[text()="Mark all as read"]');
await markRead[0].click();
const selectAll = await page.$x('//span[#role="checkbox"]');
await selectAll[1].click();
const move = await page.$x('//div[#act="8"]');
await move[0].click();
await page.waitFor(5000);
}
}
I tried using a recursion function from Nodejs Synchronous For each loop
I also tried using a function generator with yields, as well as promises and even tried the eachSeries function from the async package from this post Nodejs Puppeteer Wait to finish all code from loop
Nothing I tried was successful. Any help would be appreciated, thanks!
There is no way to visit two websites at same time with same tab. You can try it on your browser to make sure.
Jokes aside, if you want to search multiple items, you have to create a page or tab for that.
for (const search of searchTerms) {
const newTab = await browser.newPage()
// other modified code here
}
... wait that will still search one by one. But if you use a map with concurrency limit, it will work well.
We can use p-all for this.
const pAll = require('p-all');
const actions = []
for (const search of searchTerms) {
actions.push(async()=>{
const newTab = await browser.newPage()
// other modified code here
})
}
pAll(actions, {concurrency: 2}) // <-- set how many to search at once
So we are looping thru each term, and adding a new promise on the action list. Adding functions won't take much time. And then we can run the promise chain.
You will still need to modify the code above to have what you desire.
Peace!

Categories