Promises making API calls in JavaScript with async/await syntax - javascript

Trying to get a better grasp on Promises with async/await syntax. .then().catch() feels more straightforward with the added visual cues and structure.
Do these two async/await functions make any logical sense for making multiple API calls and acting on the data once they're resolved?
The goal is to have a function that takes the url, number of additional pages and returns data from all calls in one output. It works but is slow enough to lead me to believe its pretty inefficient. (I'm sure the nested loops dont help)
async function getData(url, numOfPages) {
try {
const response = await axios.get(url)
const { results, next: nextUrl } = response.data
const nextPages = await getNextPages(nextUrl, numOfPages)
const finalData = results.concat(nextPages)
finalData.forEach(data => console.log(data.name))
}
catch (err) {
console.log(err)
}
}
getData('https://swapi.dev/api/planets/', 3)
async function getNextPages(nextUrl, numOfPages) {
try {
const response = await axios.get(nextUrl)
const data = response.data.results.map(res => res)
let nextPage = response.data.next
for (let i = 1; i < numOfPages; i++) {
let response = await axios.get(nextUrl)
let results = response.data.results
nextPage = response.data.next
for (let i = 0; i < results.length; i++) {
data.push(results[i])
}
}
return data
}
catch (err) {
console.log(err)
}
}

Related

Javascript await inside a loop

I am trying to work with an api where I have to send a request for each item in a list.
However, I see that the loop doesn't seem to wait for every request, i.e, the loop doesn't work as expected. Here's the code below
getInfo = async () => {
const mylist = ["item1","item2","item3","item4","item5","item6","item7"]
const responses = []
const len = mylist.length
for (let i = 0; i < len; i++) {
//console.log("inside loop")
await axios.get("some_url/"+mylist[i])
.then(res => {
responses.push(res.data)
})
}
When I run the program, all the console.log("inside loop") executes immediately without waiting for the request to be complete.
How can I modify the code so as to wait for each response to be completed before updating the for loop counter variable?
You could try re-arranging the code to something like this. But using a Promise.all with Array.prototype.map would be more idiomatic solution for the problem.
await the async call (remove unnecessary .then call) and then console.log
getInfo = async () => {
const mylist = ["item1","item2","item3","item4","item5","item6","item7"]
const responses = []
const len = mylist.length
for (let i = 0; i < len; i++) {
responses.push((await axios.get("some_url/"+mylist[i])).data)
console.log("inside loop")
}
}
Internally, await is translated into a Promise chain. Since the for loop can't be transformed into a Promise-continuation, you'll need to convert it to a Promise-based construct.
Depending on what you want to achieve there are multiple ways to go about it.
Constructing the responses array could be done with a map statement.
const promises = mylist.map(item => {
return axios.get("some_url/"+item).then(res => { return res.data; })
});
const data = await Promise.all(promises);
No manual pushing items around or fiddling with the array length.

Looping through nested Axios GET request (pagination)

I'm querying a REST api which includes pagination. After an initial query which gets total count and max per page, I'm trying to loop through total pages to get all of the items.
const instance = axios.create({
baseURL: 'https://api.com',
headers: {'X-Application-Key': 'APPLICATION_KEY'}
});
instance.get('/trips')
.then((response) => {
const totalTrips = response.data.count;
const totalPages = Math.ceil(totalTrips / response.data.max_per_page);
let i;
for (i = 0; i < (totalPages + 1); i++) {
instance.get(`/trips?page=${i}`);
console.log(response.data.results)
};
})
.catch(function (error) {
console.log(error);
})
This isn't working since the 'response' argument is still referencing the initial query, but it's my understanding that all .then blocks should be on the same level and not actually nested, so I'm not sure how to actually loop through the pages. What is the proper way to accomplish this?
Here is a slightly opinionated response, using async await to handle the multiple asynchronous requests. And I think you'll want to use a function like promise.all, which allows you to make all the requests at the same time, and handle the response when all the http requests have completed.
const instance = axios.create({
baseURL: 'https://api.com',
headers: {
'X-Application-Key': 'APPLICATION_KEY'
}
});
getAlltrips();
async function getAlltrips() {
try {
const response = await instance.get('/trips');
const totalTrips = response.data.count;
const totalPages = Math.ceil(totalTrips / response.data.max_per_page);
const promiseArray = [];
for (let i = 0; i < (totalPages + 1); i++) {
promiseArray.push(instance.get(`/trips?page=${i}`));
};
// promise.all allows you to make multiple axios requests at the same time.
// It returns an array of the results of all your axios requests
let resolvedPromises = await Promise.all(promiseArray)
for (let i = 0; i < resolvedPromises.length; i++) {
// This will give you access to the output of each API call
console.log(resolvedPromises[i])
}
} catch (err) {
console.log('Something went wrong.');
}
}

How to return array at end of loop (Newbie question)

The following code (which I've simplified for clarity) loops through and returns the cardsToInsert before each part of the loop finishes, so the array doesn't get built properly.
The loops build the array correctly, but the result got returned near the beginning, not after it was built.
How do I get it to finish all the loops before returning the array?
async function loopCards(cardsToGet) {
for (let cardToGet of cardsToGet) {
getDataFunctionWhichReturnsAPromise()
.then(async (card) => {
writeCard = await buildingCard(card)
cardsToInsert.push(writeCard)
}
return cardsToInsert
}
thanks
Full Code Added as Requested
// wixData.get() returns a promise
async function loopCards(cardsToGet) {
let writeCard
let buildCard
for (let index = 0; index < cardsToGet.length; index++) {
const cardToGet = cardsToGet[index].card
buildCard = wixData.get("Card", cardToGet)
.then(async (card) => {
writeCard = await buildingCard(card)
cardsToInsert.push(writeCard)
}
)
.catch((err) => {
let errorMsg = err;
console.log("getcard error: " + errorMsg);
return errorMsg
});
}
return cardsToInsert
}
Here is a detailed explaination
loops are synchronous.
Promises are asynchronous.
To get the data from promises you need to wait for it to finish using callback,async-await or promise.
In your code, you are putting .then to access the result of wixData but the whole wixData.get("Card", cardToGet).then(async (card) => {}) is an async function for loopCards(cardsToGet) and because of this your loop finishes and the result array is empty.
Solution -
Just wait for wixData.get("Card", cardToGet) to finish and then do the manipulations. Here is a solution using async await.
async function loopCards(cardsToGet) {
let cardsToInsert = [];
for (let index = 0; index < cardsToGet.length; index++) {
const cardToGet = cardsToGet[index].card
let card = await wixData.get("Card", cardToGet)
let writeCard = await buildingCard(card)
cardsToInsert.push(writeCard)
}
return cardsToInsert
}
In the above code I wait for the wixData.get("Card", cardToGet) to finish and store the result in get card. This should fix your problem.
but this code by performance is not optimal as you are waiting for each network call. what you can do is execute all the Promises at once using Promise.all
Here is the code for that with error handling -
async function loopCards(cardsToGet){
try {
return await Promise.all( cardsToGet.map( cardToGet => buildCard( cardToGet.card )));
}
catch (error) {
console.log(error);
}
}
async function buildCard(cardToGet){
try {
let card = await wixData.get("Card", cardToGet);
let writeCard = await buildingCard(card);
return writeCard;
}
catch (error) {
console.log("getcard error: " + error);
return error;
}
}
The above code might have some erros since I haven't tested it but I hope you get the approach.
you should wrap loop with in promise .then or also you are using async its mean a very first item of array will be print and then it will await
getDataFunctionWhichReturnsAPromise()
.then(async (card) => {
for (let cardToGet of cardsToGet) {
writeCard = await buildingCard(card)
cardsToInsert.push(writeCard)}
}

Javascript why return empty array [duplicate]

This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 2 years ago.
I know Js is async but how to prevent my callback from returning an empty array ?
Here is my code :
function getPicturesArray(nbr,callback){
var avatarArr = [];
for(var i =0; i<nbr ; ++i){
request("https://randomuser.me/api/",{json: true},(err,response,body)=> {
avatarArr.push(body.results[0].picture.large);
});
}
callback(avatarArr);
}
getPicturesArray(12, (e)=>{
console.log(e);
});
console.log('here');
And the sout is:
[]
here
I'm mocking the calls with a public service for demonstration purposes, but the key point here is the asynchronous nature of HTTP requests.
When you invoke callback(avatarArr);, your for loop IS done but the (in this case 12) requests you sent are not, so avatarArr does not have any items yet.
Notice async before function getPicturesArray, and await before fetch
await tells the execution to wait for the fetch call to finish
You MUST check out async/await.
async function getPicturesArray(nbr,callback){
var avatarArr = [];
console.log('loading...');
for(var i =0; i<nbr ; ++i){
const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${i+1}`);
const json = await response.json();
avatarArr.push(json.title);
}
console.log('done loading...');
callback(avatarArr);
}
getPicturesArray(12, (e)=>{
console.log(e);
});
console.log('see how this is executed before done loading...');
Don`t use callbacks. Use Promises or async/await.
getUserPic = async () => {
const url = 'https://randomuser.me/api/'
const response = await fetch(url)
const users = await response.json()
return users.results[0].picture.large
}
;(async () => {
const userAvatars = []
for (let i = 0; i < 12; i++) {
userAvatars.push(await getUserPic())
}
console.log(userAvatars)
})()
Also try to use this.
In this case I advice you to use Set instead of Array to exclude duplicate items.
getUserPic = async () => {
const url = 'https://randomuser.me/api/'
const response = await fetch(url)
const users = await response.json()
return users.results[0].picture.large
}
getMultipleUserPics = async limit => {
const userAvatars = new Set()
while (userAvatars.size != limit) {
userAvatars.add(await getUserPic())
}
return userAvatars
}
;(async () => {
const userPics = await getMultipleUserPics(5)
console.log(userPics)
})()

Awaiting for a for loop to end to continue function in javascript

i have one question! I want the function getMealSummary() to return after the for loop has finished already finished (I didnt include the return key because the function is very big and i just had a doubt in this section).
How can I do it with async/await or promises?
Thanks
export const getMealMicroSummaryHelper = async function getMealSummary(array, populatedFoods, customMeasurements, isRecipe,expertId){
var [totalZinc, totalCalcium,totalAlphaCarotene,totalBetaCarotene,totalCholine,totalCopper,totalBetaCrypto,totalFluoride,totalVitaminB9,totalIron,
totalLutein,totalLycopene,totalMagnesium,totalManganese,totalNiacin,totalVitaminB5,totalPhosphorus,totalPotassium,totalRetinol,totalRiboflavin,
totalSelenium,totalSodium,totalTheobromine,totalVitaminA,totalVitaminB12,totalVitaminB6,totalVitaminC,totalVitaminD,totalVitaminD2,totalVitaminD3,
totalVitaminE,totalThiamin] = [0, 0, 0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
let recipesArray = []
for(let i = 0; i < populatedFoods.length; i++){
if(populatedFoods[i].foodType === 4){
await apiCall("get",`/api/experts/${expertId}/recipes/${populatedFoods[i]._id}`)
.then(recipe =>{
recipesArray.push(recipe)
})
.catch((err)=>console.log(err))
}
You can use a combination of map() and Promise.all for this like:
async function getMealSummary(array, populatedFoods, customMeasurements, isRecipe, expertId) {
try {
let recipesArray = await Promise.all(
populatedFoods.map(async populatedFoods => {
if (populatedFoods[i].foodType === 4) {
let recipe = await apiCall("get", `/api/experts/${expertId}/recipes/${populatedFoods[i]._id}`)
return recipe;
}
})
)
} catch (err) {
console.log(err)
}
}
This is just a simple version to give you a basic idea. I have not included all the variables that you have declared above recipesArray. Those will need to be included as needed.

Categories

Resources