How can I use map data with async await - javascript

I want to .map instead of for loop but I still confuse how to use it here is my data
const arr = [
{
name: "John",
subject : [
{
subject_id : 1,
score :20
}
]
},
{
name: "Doe",
subject : [
{
subject_id : 2,
score :20
}
]
}
]
I want to add subject detail into subject array
async getData() {
for (let i in arr) {
let data = arr[i];
for (let i in data.subject) {
const subject = data.subject[i];
//this line code will get subject detail
const getSubject = await this.getSubject(subject);
subject["subjectData"] = getSubject;
}
}
}
everything work fine but How can I use map instead of for loop
example for output
{
name: "Doe",
subject : [
{
subject_id : 2,
score :20,
subjectData: {
//some detail
}
}
]
}
Here is what I want to do
const newData = arr.map(async data => {
const getSubject = data.subject;
const subject = getSubject.map(async zp02 => {
const getSubject = await this.getSubject(subject);
return { getSubject };
});
return { ...data, subject };
});
my subject always return null .
the reason that I want to use map because I've read many people say It faster than for loop
Is their anyway to use map instead of for
I'm learning about clean code can you give me an Idea how to refactor code in nested for loop

Check this up https://dev.to/jhalvorson/how-do-i-use-async-await-with-array-map-1h3f.
From this article:
You can't async/await Array.map since synchronous code won't sit and wait for your asynchronous code to resolve, instead it will the fire the function and move on. This is desired behaviour as we don't want our Array.map to block the thread otherwise nothing would run whilst Array.map goes and does its thing. Knowing that is cool, however, it doesn't really help us if we need to await our Array.map().
Thankfully there is a solution, we can achieve what we want by utilising Promise.all
//For example, the following won't work:
const postIds = ['123', 'dn28e29', 'dn22je3'];
const posts = await postIds.map(id => {
return axios
.get(`https://example.com/post/${id}`)
.then(res => res.data)
.catch(e => console.error(e));
}
console.log(posts) // [] it returns the promise, not the results 💩
//But this will:
const postIds = ['123', 'dn28e29', 'dn22je3'];
const posts = posts.map(post => {
return axios
.get(`https://example.com/post/${post.id}`)
.then(res => res.data)
.catch(e => console.error(e));
}
Promise.all(posts).then(res => console.log(`We have posts: ${res}!`));
Instead of immediately returning the Array.map we're assigning it to a variable and passing that variable to Promise.all, once that resolves we then have access to the new array returned by Array.map.

Related

JS/React - How should I use a fetch call inside of a for loop?

I'm still a little unfamiliar with asynchronous functions beyond their simplest forms, so I'm hoping someone here can help me out.
Some information:
getCollection() returns an array of objects that look like this.
{
"id": 1,
"userId": 3,
"gameId": 3498,
"review": "Testing review for game 1"
},
getGameById() takes in a integer (the id of a game) and returns that game object from an external API.
gameArray should be getting filled with game objects whose IDs in the external API match the IDs from the array of objects provided by getCollection.
const [games, setGames] = useState([])
const getGames = () => {
getCollection().then(userGames => {
let gameArray = []
for (const eachObj of userGames) {
if (eachObj.userId === currentUserId) {
getGameById(eachObj.gameId).then(game => {
gameArray.push(game)
})
}
}
Promise.all(gameArray).then(() => {
console.log(gameArray) //logs empty array, but shouldn't be empty
setGames(gameArray)
})
})
}
I have never used Promise.all before, it's just something I saw as a possible solution to this issue I'm having.
Promise.all takes an array of promises.
First you must build the array of promises. Then you must call Promise.all with this array to retrieve all the games :
function getGames() {
getCollection().then(userGames => {
const gamePromises = userGames
.filter(userGame => userGame.userId == currenUserId)
.map(userGame => getGameById(userGame.gameId));
Promise.all(gamePromises).then(games=> {
console.log(games);
setGames(games)
});
})
}
Here is another solution using async function which is maybe more readable
async function getGames() {
const userGames = await getCollection();
const currentUserGames = userGames.filter(({userId}) => userId == currentUserId);
const games = await Promise.all(userGames.map(({gameId}) => getGameById(gameId));
setGames(games);
}
The array that you pass to Promise.all needs to contain the promises, but you're pushing the game objects - and you're doing it asynchronously, so the array is still empty when you pass it to Promise.all.
const getGames = () => {
getCollection().then(userGames => {
let gamePromises = []
for (const eachObj of userGames) {
if (eachObj.userId === currentUserId) {
gamePromises.push(getGameById(eachObj.gameId))
// ^^^^^^^^^^^^^^^^^^ ^
}
}
return Promise.all(gamePromises)
// ^^^^^^ chaining
}).then(gameArray => {
// ^^^^^^^^^
console.log(gameArray)
setGames(gameArray)
})
}
To simplify:
async function getGames() {
const userGames = await getCollection()
const gamePromises = userGames
.filter(eachObj => eachObj.userId === currentUserId)
.map(eachObj => getGameById(eachObj.gameId))
const gameArray = await Promise.all(gamePromises)
console.log(gameArray)
setGames(gameArray)
}

React JS multiple API calls, data undefined or unexpected reserved word 'await' mapping through the data:

I'm creating a JS function that will make a call to an API, loop through the returned data and perform another call to retrieve more information about the initial data (for example where the first call return an ID, the second call would return the name/address/number the ID corresponds to). Positioning the async and await keywords though, have proven to be way more challenging than I imagined:
useEffect(() => {
const getAppointments = async () => {
try {
const { data } = await fetchContext.authAxios.get('/appointments/' + auth.authState.id);
const updatedData = await data.map(value => {
const { data } = fetchContext.authAxios.get('/customerID/' + value.customerID);
return {
...value, // de-structuring
customerID: data
}
}
);
setAppointments(updatedData);
} catch (err) {
console.log(err);
}
};
getAppointments();
}, [fetchContext]);
Everything get displayed besides the customerID, that results undefined. I tried to position and add the async/await keywords in different places, nothing works. What am I missing?
map returns an array, not a promise. You need to get an array of promises and then solve it (also, if your way worked, it would be inefficient waitting for a request to then start the next one.)
const promises = data.map(async (value) => {
const { data } = await fetchContext.authAxios.get('/customerID/' + value.customerID);
return {
...value,
customerID: data
};
});
const updatedData = await Promise.all(promises);

async Array.map() inside another map call

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' } ] }]

async and await in array of many objects ie more than 1000

I Have more than 1000 array of objects of these properties like:
const empArr = [{
empName: 'sds',
empId: 'e3',
dob: '22-12-2',
.. so on
}];
I have tried the problem statement using promise.all(promises) but i want to do it more efficiently .So, I need some help
function makeDBInsert(empArr) {
const dbArr = [];
const promises = [];
const LEN = empArr.length;
/** this function returns promise containing emp age **/
function getEmpAge(empId) {
return db.execute('select age from emp where "empId" = ?', [empId]);
}
for (let i = 0; i < LEN; i += 1) {
const obj = {
name: empArr[i].empName,
empId: empArr[i].empId
}
promises.push(getEmpAge(obj.empId);
dbArr.push(obj);
}
return Promise.all(promises).then((empDetail) => {
const dbObj = empDetail.map((emp, i) => {
return {
dbArr[i].age: emp.age
}
});
return db.insert('insert into emp ("name", "id", "age") values ('?', '?', '?');', Object.values(obj));
}).catch(err => console.error(err));
}
So, here empArr has more than 1000 objects in the array. Here, I am fetching emp age from db that returns me promise containing emp id. I am using promise.all to do this work, instead is there any better way doing without using promise.all and then iterating ? Thanks in Advance.
Note: Please ignore syntactical errors in the code. My aim here is just to describe my problem statement.
I got the solution ie one loop do everything like using IIFE, async await, no promise.all and iterating on the promises response.
async function makeDBInsert(empArr) {
const modifiedArr = [];
function getEmpAge(empId) {
return db.execute('select age from emp where "empId" = ?', [empId]);
}
await (function doDBCalls() {
empArr.forEach(async (item, i) => {
/* getEmpAge(param) is DB call */
const res = await getEmpAge(item.empId);
modifiedArr.push({modified: res.age });
// modify the object here and batch insert
});
})();
}

Array of Promises, passing variables used in creation

Using Express, I am using .map to create an array of API calls, whose results I want to combine into one response object. Since every call uses different query parameters, I would like to use the query parameters as the response object's keys.
I create the GET requests using axios, which returns a promise, and then I use axios.all to wait for all of the promises to resolve.
The problem is that after the promises resolve, I no longer have access to the variables used to create them. How can I attach those variables to the promises for later reference?
Here is the API:
router.get('/api', (req, res) => {
const number = req.query.number;
res.json({ content: "Result for " + number });
});
Here is where I am trying to combine the results:
router.get('/array', async (req, res) => {
res.locals.payload = {};
const arr = [1, 2, 3, 4, 5];
const promises = arr.map(number => {
return axiosInstance.get('/api', { params: { number: number } })
});
const results = await axios.all(promises);
results.map(r => {
// number should match original, but I no longer have
// access to the original variable
number = 1;
res.locals.payload[number] = r.data;
});
res.json(res.locals.payload);
});
Result of GET on /array:
{
"1": {
"content": "Result for 5"
}
}
What can I do when creating the Promise objects to preserve the keys?
If the result were going to be an array with 0-based indexes instead of an object with properties "1", "2", etc., we could solve it with Promise.all (or axios.all if it offers the same guarantee Promise.all does that the array it gives you will be in the order of the promises you give it, regardless of the order in which they resolve). But I'm guessing your "number" is really a placeholder for something more interesting. :-)
You can do this by making use of the fact that a promise is a pipeline where the various handlers transform the contents along the way.
In this case, you can transform the result of the get call into an object with the number and result:
const promises = arr.map(number => {
return axiosInstance.get('/api', { params: { number } })
.then(result => ({result, number}));
});
Now, results in your all.then callback receives an array of objects with result and number properties. You could use destructuring parameters to receive them in your results.map callback if you like:
// vvvvvvvvvvvvvvvv---------------- destructuring parameters
results.map(({result, number}) => {
res.locals.payload[number] = result.data;
// ^^^^^^----^^^^^^-------- using them
});
Live Example:
// Fake axiosInstance
const axiosInstance = {
get(url, options) {
return new Promise(resolve => {
setTimeout(() => {
resolve({data: `Data for #${options.params.number}`});
}, 300 + Math.floor(Math.random() * 500));
});
}
};
// Fake axios.all:
const axios = {
all: Promise.all.bind(Promise)
};
// Fake router.get callback:
async function routerGet() {
const payload = {}; // stand-in for res.locals.payload
const arr = [1, 2, 3, 4, 5];
const promises = arr.map(
number => axiosInstance.get('/api', { params: { number } })
.then(result => ({result, number}))
);
const results = await axios.all(promises);
results.map(({result, number}) => {
/*res.locals.*/payload[number] = result.data;
});
console.log(/*res.locals.*/payload);
}
// Test it
routerGet().catch(e => { console.error(e); });
Note I've used ES2015+ property shorthand above, since you're using arrow functions and such. The object initializer { number } is exactly the same as { number: number }.
Side note 2: You can use a concise arrow function for the map callback if you like:
const promises = arr.map(
number => axiosInstance.get('/api', { params: { number } })
.then(result => ({result, number}))
);

Categories