This is a follow up to this question.
I think I've solved the async problem. However, only the first function is getting fulfilled. I have log out both function calls but only the first displays the collection in the console. The second shows an empty array until I click to expand, then it shows the data.
Here is my code currently:
import {
flowersThumbRef,
flowersFullRef,
} from "../../../../utils/firebase/firebase";
// gets a list of all items in a firebase storage, builds an object, and pushes it into an array and returns the array.
export const getAllImages = async () => {
let images = [];
const response = await flowersFullRef.listAll();
response.items.forEach(async (item) => {
const imgUrl = await item.getDownloadURL();
images.push({
name: item.name,
link: imgUrl,
});
});
return images;
};
// gets a list of all items in a firebase storage, builds an object, and pushes it into an array and returns the array.
export const getAllThumbnails = async () => {
let images = [];
const response = await flowersThumbRef.listAll();
response.items.forEach(async (item) => {
const imgUrl = await item.getDownloadURL();
images.push({
name: item.name,
link: imgUrl,
});
});
return images;
};
// executes previous functions.
export const createItems = async () => {
const allImages = await getAllImages();
const allThumbs = await getAllThumbnails();
console.log(allImages);
console.log(allThumbs);
};
createItems();
I realize that this question is similar to the previous linked question, but I think I'm running into a different issue. Or there's some quirk of async JS that I'm missing.
Thanks!!
So actually both are wrong :O. The reason the first seems ok, is because by the time you reach for it the promises have resolved, but if the way you coded this is not correct. The problem you are having is with the use of .forEach. .forEach is spinning up many async functions (this section right here async (element) => {}) and you are not awaiting each individual promise (PS: you wouldn't be able to with a .forEach loop). To fix this change the code to
import {
flowersThumbRef,
flowersFullRef,
} from "../../../../utils/firebase/firebase";
// gets a list of all items in a firebase storage, builds an object, and pushes it into an array and returns the array.
export const getAllImages = async () => {
let images = [];
const response = await flowersFullRef.listAll();
for(let i = 0; i < response.items.length; i++){
const item = response.items[i];
const imgUrl = await item.getDownloadURL();
images.push({
name: item.name,
link: imgUrl,
});
});
return images;
};
// gets a list of all items in a firebase storage, builds an object, and pushes it into an array and returns the array.
export const getAllThumbnails = async () => {
let images = [];
const response = await flowersThumbRef.listAll();
for(let i = 0; i < response.items.length; i++){
const item = response.items[i];
const imgUrl = await item.getDownloadURL();
images.push({
name: item.name,
link: imgUrl,
});
});
return images;
};
// executes previous functions.
export const createItems = async () => {
const allImages = await getAllImages();
const allThumbs = await getAllThumbnails();
console.log(allImages);
console.log(allThumbs);
};
createItems();
Related
Problem
I am trying to call a database function while mapping an array. I created some sample code to demonstrate my issue on a smaller scale. I am using the 'mysql2' and 'sequelize' dependencies, as well as MySQL Workbench.
Goal in Sample Code
I have two tables in my database - one called 'boxes' and one called 'items'. Each item will be in a box (it will have a property of 'box_id'). Each box has a location. I want to get an array of objects that simply shows the name of the item and the location of the box it's in (not just the ID). I want this to be run in the cleanest way possible. I am aware there's a way to relate the two databases together, and an answer on how to do that would be appreciated, but I feel I would learn more important concepts using a different method - so ideally, I would like an alternative solution.
Code
mainFunction() is the starting point
// Gets all items currently in my database and returning the dataValues
const getAllItems = async () => {
const findAllItems = await Item.findAll()
const items = findAllItems.map(item => item.dataValues)
return items
}
// Gets a box from my database, by ID, and returning its dataValues
const getOneBox = async (id) => {
const findOneBox = await Box.findOne({ where: {id}})
return findOneBox.dataValues
}
// Starting point
const mainFunction = async () => {
// Get all items in database
const items = await getAllItems()
// Go through each item, and everytime, get the box that corresponds to the item's box_id
const myArray = items.map(async item => {
const getBox = await getOneBox(item.box_id)
// Return an object with my custom formatting, and including the box's location
return {
itemID: item.id,
itemName: item.name,
itemLocation: getBox.location
}
})
// The function will only work if I manually give my function enough time to finish
console.log('myArray before delay => ', myArray)
await new Promise(response => setTimeout(response, 500))
console.log('myArray after delay => ', myArray)
}
Here is the result in my terminal:
Setup
Here is my setup if it matters. I populated my tables manually to simplify things:
items =>
boxes =>
// Populating the existing 'test' schema with relevant tables and running main function after connecting
const Sequelize = require('sequelize')
const database = new Sequelize ('test', 'root', [REDACTED], {
host: 'localhost',
dialect: 'mysql',
define: {
timestamps: false
}
})
const connect = async () => {
await database.authenticate()
.then(async () => {
await database.sync()
console.log('Connected...')
mainFunction()
}).catch(error => {
console.log('Failed to connect => ', error)
})
}
connect()
// Defining my models
const Box = database.define('box', {
name: Sequelize.STRING,
location: Sequelize.STRING
})
const Item = database.define('item', {
name: Sequelize.STRING,
box_id: Sequelize.INTEGER
})
Turns out the issue was in my approach; it's actually very easy with for loops. I'll leave my solution in case it helps anyone.
This one just adds another property to my items array
const mainFunction = async () => {
const items = await getAllItems()
for(i = 0; i < items.length; i++) {
const getBox = await getOneBox(items[i].box_id)
items[i]['location'] = getBox.location
}
console.log(items)
}
This one is for if I wanted to format it in my own way
const mainFunction = async () => {
const items = await getAllItems()
const itemsFormatted = []
for(i = 0; i < items.length; i++) {
const getBox = await getOneBox(items[i].box_id)
itemsFormatted.push({
itemID: items[i].id,
itemName: items[i].name,
itemLocation: getBox.location
})
}
console.log(itemsFormatted)
}
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Best way to call an asynchronous function within map?
(14 answers)
Closed 2 years ago.
I want to fetch data with API. Fecth is my utils, the get API data. It working the only array so I used the fetch function in the javascript the sample object below. My utils is not supported object.
The problem I'm having is not related to utils. The problem is array push in async function.
Array push is not working the map function outside :(
But, is working in the map function :)
What should I do to run outside?
/* eslint-disable */
import Fetch from '#/utils/fetch';
async function allPosts(req, res) {
const myPostsAll = await Fetch(`${process.env.NEXT_PUBLIC_API_URL}/${process.env.NEXT_PUBLIC_API_USERNAME}`);
const posts = [];
myPostsAll.map(async (item) => {
const { id } = item;
const myPostRes = await fetch(`${process.env.API_URL}/${id}`);
const myPost = await myPostRes.json();
const { title, description, cover_image, body_html } = myPost;
posts.push({
id: id.toString(),
title,
thumbnail: cover_image,
description,
content: body_html,
});
// It's work
console.log(posts);
});
// It's not work
console.log(posts);
}
The best way to go about this is to utilize Promise.all:
async function allPosts(req, res) {
const myPostsAll = await Fetch(`...`);
// use `Array.map` to create an array of
// `Promise`s, then pass that array as an argument
// to `Promise.all` which will wait for all
// `Promise`s to resolve and return an array of objects
const posts = await Promise.all(
myPostsAll.map(async (item) => {
const { id } = item;
const myPostRes = await fetch(`${process.env.API_URL}/${id}`);
const myPost = await myPostRes.json();
const { title, description, cover_image, body_html } = myPost;
return {
id: id.toString(),
title,
description,
thumbnail: cover_image,
content: body_html,
}
})
);
console.log(posts);
}
Also, the reason why your current solution doesn't work is because there's nothing in your code that says to wait for myPostsAll to finish before calling console.log(posts) at the end of your function outside of the callback of the Array.map function:
async function test() {
const posts = [];
// this is asynchronous, so it won't "wait"
// to finish before moving to the next statement,
// unless you apply the solution above with `Promise.all`
// and `await`
myPostsAll.map(async () => {
const myPostRes = await fetch(`...`);
posts.push(...);
})
// gets called prior to `myPostsAll.map` completing
console.log("posts", posts);
}
References:
Promise.all - MDN
Try this way:
/* eslint-disable */
import Fetch from '#/utils/fetch';
async function allPosts(req, res) {
const myPostsAll = await Fetch(`${process.env.NEXT_PUBLIC_API_URL}/${process.env.NEXT_PUBLIC_API_USERNAME}`);
const posts=myPostsAll.map(async (item) => {
const { id } = item;
const myPostRes = await fetch(`${process.env.API_URL}/${id}`);
const myPost = await myPostRes.json();
const { title, description, cover_image, body_html } = myPost;
return {
id: id.toString(),
title:title,
thumbnail: cover_image,
description,
content: body_html}
});
// It's not work
console.log(posts);
}
I have below code in node. In getPosts, it reads 10 posts from database which is an async function call. And for each post, it needs to read user info. from database which is another async function call. How can I make it work in node js?
const getUser = async (userId) => {
// read user from database
}
const getPosts =async () => {
const posts = await getPostsFromDB(10); // get 10 posts from database
for(let i=0; i<posts.length; i++){
posts[i].user = await getUser(posts[i].userId) // ERROR: I can't call await inside a loop
}
}
I am thinking about using Promise.all() like below:
const getPosts =async () => {
const posts = await getPostsFromDB(10); // get 10 posts from database
const allProms = posts.map(post => getUser(post.userId));
Promise.all(allProms); // how can I assign each user to each post?
}
but I don't know how I can assign each user to each post after calling Promise.all().
Consider approaching the problem slightly differently. If you wait for responses in an iterative loop, it'll produce poor performance. Instead, you could push them all into an array and wait for them — so they're all fetching at the same time.
const getUser = async (userId) => {
try {
// read
} catch (e) {
// catch errors
}
// return data
}
const getPosts = async () => {
const posts = await getPostsFromDB(10); // get 10 posts from database
const userRequests = posts.map((post, index) => getUser(post.userId))
const users = await Promise.all(userRequests)
return posts.map((post, index) => {
post.user = users[index]
})
}
If you think you may have duplicate userIds, consider forming a list of users you can reference before calling getUser.
I have a method that receives a profiles array and I have to map for each profile and inside this map I have to map again in the photos propriety, which contains the pictures ids for requesting to an API for getting this picture.
The question is, where can I safely access this profiles array with their loaded photos for each respective profile?
profiles.map((profile, i) => {
let photos = []
Promise.all(profile.photos.map(async idPhoto => {
const res = await fetch(...)
const img = await res.blob()
photos.push(img)
}))
.then(() => profiles[i].photos = [...photos])
})
With the outer map function the way it currently is, the Promise.all() calls are discarded, so there is no way for your code to detect when they are complete.
However, since you also do not appear to be using the return value of the outer map, we can make it return an array of Promises that resolve when the inner their array of Promises is all resolved. And then we can use the same Promise.all(array.map()) pattern as we use for the inner map.
const photoRequests = profiles.map(async (profile, i) => {
let photos = []
await Promise.all(profile.photos.map(async idPhoto => {
const res = await fetch(...)
const img = await res.blob()
photos.push(img)
}));
profiles[i].photos = [...photos];
})
// And now...
await Promise.all(photoRequests);
// After this it is safe to access.
// Or, if the outer map is not in an async method:
Promise.all(photoRequests).then(() => {
// It is safe to access profiles here
});
I've refactored the outer map to be an async function (aids readability IMO), but you can put it back if you prefer. Just have the outer map function return the result of the Promise.all call.
As to what else could be improved here, the having variables photos and profile.photos is a little confusing, so consider renaming photos. Also make it const while you're at it, as it's never reassigned.
Unless there's some other code that manipulates the photos array, the array spread syntax is not needed. Same for the index variable. Final code might look something like:
const photoRequests = profiles.map(async profile => {
const loadedPhotos = []
await Promise.all(profile.photos.map(async idPhoto => {
const res = await fetch(...)
const img = await res.blob()
loadedPhotos.push(img)
}));
profile.photos = loadedPhotos;
})
await Promise.all(photoRequests);
Or you could use the fact that Promise.all resolves to an array containing the resolve values of the individual promises it received:
const photoRequests = profiles.map(async profile => {
profile.photos = await Promise.all(
profile.photos.map(async idPhoto => {
const res = await fetch(...)
return res.blob()
})
);
})
await Promise.all(photoRequests);
I think it would be better to separate each mapping into its own function, it makes it easier to read. I refactored your code into this:
let fetchPhoto = async (photoId) => {
// const res = await fetch(...);
// return res.blob();
return { imageData: photoId } // mock blob result
};
let mapPhotoIdToImage = async (profile) => {
let photos = profile.photos.map(fetchPhoto)
photos = await Promise.all(photos);
profile.photos = photos;
return profile;
};
let profileList = [{photos: ['id1', 'id2']}];
let result = await profileList.map(mapPhotoIdToImage);
result:
[{ photos: [ { imageData: 'id1' }, { imageData: 'id2' } ] }]
I need to merge data from API. I do a first call to an endpoint that gives me a list of ids, then I do a request for each id. My goal is to return a list with the responses of all requests but I lost myself in promises ...
My code runs on NodeJS. Here is the code :
const fetch = require('node-fetch')
const main = (req, res) => {
fetch('ENDPOINT_THAT_GIVES_LIST_OF_IDS')
.then(response => response.json())
.then(response => {
parseIds(response)
.then(data => {
console.log(data)
res.json(data)
// I want data contains the list of responses
})
})
.catch(error => console.error(error))
}
const getAdditionalInformations = async function(id) {
let response = await fetch('CUSTOM_URL&q='+id, {
method: 'GET',
});
response = await response.json();
return response
}
const parseIds = (async raw_ids=> {
let ids= []
raw_ids.forEach(function(raw_id) {
let informations = {
// Object with data from the first request
}
let additionalInformations = await
getAdditionalInformations(raw_id['id'])
let merged = {...informations, ...additionalInformations}
ids.push(merged)
})
return ids
})
main()
I get this error : "await is only valid in async function" for this line :
let additionalInformations = await getAdditionalInformations(raw_id['id'])
Help me with promise and async/await please.
You're almost there, just a slight bit of error here with your parentheses:
// notice the parentheses'
const parseIds = async (raw_ids) => {
let ids= []
raw_ids.forEach(function(raw_id) {
let informations = {
// Object with data from the first request
}
let additionalInformations = await getAdditionalInformations(raw_id['id'])
let merged = {...informations, ...additionalInformations}
ids.push(merged)
})
return ids
}
You are missing an async after forEach
const parseIds = (async raw_ids=> {
let ids= []
raw_ids.forEach(async function(raw_id) {
let informations = {
// Object with data from the first request
}
let additionalInformations = await
getAdditionalInformations(raw_id['id'])
let merged = {...informations, ...additionalInformations}
ids.push(merged)
})
return ids
})
One suggestion: you are mixing promises (.then()) with async/await. Prefer async/await is more readable.
Note that getAdditionalInformations inside forEach doesn't wait for it to be done before going to the next entry of the array.
You can use plain old for(var i=0; .... instead