returning mapped array from chained promises - javascript

function createDataSet(username, region, champion, amount) {
var dataArray = []; //what I want to return, if possible with .map()
return getUserId(username, region) //required for getUserMatchlist()
.then(userId => {
getUserMatchlist(userId, region, champion, amount); //returns an array of objects
})
.then(matchlist => {
matchlist.forEach(match => {
getMatchDetails(match.gameId.toString(), region) //uses the Id from the matchlist objects to make another api request for each object
.then(res => {
dataArray.push(res); //every res is also an object fetched individually from the api.
// I would like to return an array with all the res objects in the order they appear in
})
.catch(err => console.log(err));
});
});
}
I'm trying to send data that I fetched from multiple apis to my frontend. Fetching the data isn't a problem, however, using .map() didn't work and from what I've read doesn't work well with promises. What is the best way for me to return that object? (function will be executed when a get request is received and dataArray will be sent back)

Promise.all(listOfPromises) will resolve to an array containing the resolved result of each promise in listOfPromises.
To apply that to your code, you would want something like (pseudocode):
Promise.all(matchlist.map(match => getMatchDetails(...)))
.then(listOfMatchDetails => {
// do stuff with your list!
});

Related

Question about asynchronous JavaScript with Promise

Here I have a function that takes an array of string that contains the user names of github accounts. And this function is going to return an array of user data after resolving. There should be one fetch request per user. and requests shouldn’t wait for each other. So that the data arrives as soon as possible. If there’s no such user, the function should return null in the resulting array.
An example for the input would be ["iliakan", "remy", "no.such.users"], and the expected returned promise after resolving would give us [null, Object, Object], Object being the data that contained info about a user.
Here is my attempt to solve this question.
function getUsers(names) {
return new Promise(resolve => {
const array = [];
const url = "https://api.github.com/users/";
const requests = names.map(name => {
const endpoint = `${url}${name}`;
return fetch(endpoint);
});
Promise.all(requests).then(reponses => {
reponses.forEach(response => {
if (response.status === 200) {
response.json().then(data => {
array.push(data);
});
} else {
array.push(null);
}
});
resolve(array);
});
});
}
It does work, i.e. returning an array [null, Object, Object]. And I thought it fulfilled the requirements I stated above. However, after looking at it closely, I felt like I couldn't fully make sense of it.
My question is, look at where we resolve this array, it resolved immediately after the forEach loop. One thing I don't understand is, why does it contain all three items when some of the items are pushed into it asynchronously after the json() is finished. what I mean is, in the case where response.status === 200, the array is pushed with the data resolved from json(), and I would assume this json() operation should take some time. Since we didn't resolve the array after json() operation is finished, how come we still ended up with all data resolved from json()?
Promise.all(requests).then(reponses => {
reponses.forEach(response => {
if (response.status === 200) {
response.json().then(data => {
array.push(data); <--- this should take some time
});
} else {
array.push(null);
}
});
resolve(array); <--- resolve the array immediately after the `forEach` loop
});
});
It looks to me like the array we get should only have one null in it since at the time it is revolved, the .json() should not be finished
You're right, the result is pushed later into the array.
Try to execute this:
const test = await getUsers(['Guerric-P']);
console.log(test.length);
You'll notice it displays 0. Before the result is pushed into the array, its length is 0. You probably think it works because you click on the array in the console, after the result has arrived.
You should do something like this:
function getUsers(names) {
const array = [];
const url = "https://api.github.com/users/";
const requests = names.map(name => {
const endpoint = `${url}${name}`;
return fetch(endpoint);
});
return Promise.all(requests).then(responses => Promise.all(responses.map(x => x.status === 200 ? x.json() : null)));
};
You should avoid using the Promise constructor directly. Here, we don't need to use it at all.
const url = "https://api.github.com/users/";
const getUsers = names =>
Promise.all(names.map(name =>
fetch(url + name).then(response =>
response.status === 200 ? response.json() : null)));
getUsers(["iliakan", "remy", "no.such.users"]).then(console.log);
The Promise constructor should only be used when you're creating new kinds of asynchronous tasks. In this case, you don't need to use the Promise constructor because fetch already returns a promise.
You also don't need to maintain an array and push to it because Promise.all resolves to an array. Finally, you don't need to map over the result of Promise.all. You can transform the promises returned by fetch.
The thing is that because json() operation is really quick, especially if response data is small in size it just has the time to execute. Second of all as objects in JavaScript passed by reference and not by value and Array is a object in JavaScript, independently of execution time it'll still push that data to the array even after it was resolved.

In a array of promises, how to access the ID used to fetch data

Promise.all(arrayOfIds.map((id) =>
axios.get(url);
)).then((responses) => {
/* here want to access the respective id used for fetching data,
so that I can add an extra property in to the corresponding response
*/
});
Array of promises to concatenate input data with the response
A simple loop should get you what you want:
Promise.all(arrayOfIds.map((id) =>
axios.get(url);
)).then((responses) => {
/* here want to access the respective id used for fetching data,
so that I can add an extra property in to the corresponding response
*/
responses.forEach((response, index) => {
response.id = arrayOfIds[index];
});
// do something with responses
});
of if you prefer a manual loop instead of forEach:
Promise.all(arrayOfIds.map((id) =>
axios.get(url);
)).then((responses) => {
/* here want to access the respective id used for fetching data,
so that I can add an extra property in to the corresponding response
*/
for (let i = 0; i < responses.length; i++) {
responses[i].id = arrayOfIds[i];
}
});
The reason this works is that Promise.all guarantees that the resolved values will be in the same order as the array of promises passed to it, which in your case is the result of a map() call, which preserves the order.
So everything should line up in the end.
You can also return a Promise.all, where one item in the passed array is the id, and the other is the axios.get Promise:
Promise.all(arrayOfIds.map((id) => Promise.all([
id,
axios.get(url)
]))).then((responses) => {
for (const [id, response] of responses) {
// ...
}
});

How to save firebase returned data to a variable

I am coding for my React Native app and I am having trouble getting the data from the firebase return outside of the firebase.firestore().collection("test_data").doc(ID) loop. Whenever I check the dataArray variable after the loop it is empty. If I check it within the loop, the data is there. I think that it is a scope problem, but I just do not understand it. I also cannot call any user defined functions inside of the loop.
try {
let dataArray = [];
// get the document using the user's uid
firebase.firestore().collection("users").doc(uid).get()
.then((userDoc) =>{
// if the document exists loop through the results
if (userDoc.exists) {
data = userDoc.data().saved_data; // an array store in firebase
data.forEach(ID => { // loop through array
firebase.firestore().collection("test_data").doc(ID).get()
.then((doc) => {
dataArray.push(doc.data().test_data);
console.log(dataArray) // the data shows
})
console.log(dataArray) // the data does not show
})
}
})
}
catch (error) {
}
}
You're looping through asynchronous calls, so your final console.log will trigger before the data has been received. Your first console.log only triggers after the data has been received.
So the code is working, but the function (promise) will resolve (as undefined, or void) before all the data has been received from your firebase calls.
If you want to return the array to the caller, you could do something like this:
function getDataFromServer() {
// get the document using the user's uid
return firebase.firestore().collection('users').doc(uid).get().then(userDoc => {
// now we're returning this promise
const dataArray = []; // can move this to lower scope
// if the document exists loop through the results
if (userDoc.exists) {
const savedData = userDoc.data().saved_data; // an array store in firebase
return Promise.all(
// wait for all the data to come in using Promise.all and map
savedData.map(ID => {
// this will create an array
return firebase.firestore().collection('test_data').doc(ID).get().then(doc => {
// each index in the array is a promise
dataArray.push(doc.data().test_data);
console.log(dataArray); // the data shows
});
})
).then(() => {
console.log(dataArray);
return dataArray; // now data array gets returned to the caller
});
}
return dataArray; // will always be the original empty array
});
}
Now the function returns the promise of an array, so you could do...
const dataArray = await getDataFromServer()
or
getDataArrayFromServer().then(dataArray => {...})

return array instead of objects laravel/javascript ajax call

I am creating a front-end Vue component. For this I am using data and a database from a Laravel project. The problem I got is that a method from Laravel (And so does the AJAX call) is returning an Array, which is good, and the other method is returning just the objects (collection?) with the AJAX call. I need both calls returning an Array because I am using some Array functions in my component.
I've tried all sort of things, both on the front-end as back-end. I've tried on the backend to let laravel return an array and on the Front-end I tried to set the objects to an array. Both didn't succeed.
My JS code (And AJAX calls)
methods: {
countAllItems() {
this.countItems = this.items.length;
},
getUrl(items) {
let results = items;
results.forEach((item) => {
let id = item.id;
return item.url = `/advertisements/show/${id}`;
});
},
getMax(items) {
let boats = items.slice(0, 4);
this.items = boats;
this.getUrl(this.items);
this.countAllItems();
},
getBoats() {
axios.get('/api/boats')
.then((response) => {
//this will return an array
console.log(response.data);
this.items = response.data;
this.countAllItems();
this.getMax(this.items);
})
.catch((error) => console.log(error));
},
getReservations() {
axios.get('/api/popular')
.then((response) => {
//this will return objects, and must be an array
console.log(response.data);
this.items = response.data;
this.countAllItems();
this.getMax(this.items);
});
}
},
created() {
this.getBoats();
this.getReservations();
}
The back-end
//This will return the array
public function boats() {
return Boat::with('media')->get();
}
//This will return objects
public function messages() {
$boats = Boat::with('media')->get()->sortByDesc(function($boat) {
return count($boat->conversations);
});
return $boats;
}
Is there someone who can help me out? So I need either a fix for returning an array in the Laravel method, or something like a function to push both objects to an array in JavaScript.
Edit
So I tried some things and doing that something occured to me. When I do dd($boats) it will return a Collection. When I do dd($boats->toArray) it returns an array. What the weird thing is is that it makes no difference tho in the AJAX calls. So $boats->toArray() will still return only the objects and not the objects in an array.
I also tried to push the objects in an array but that doesn't work either. It will place the collection (as a object) into an array. How can I change this that the objects will directly be inserted in the array instead of the collection as a object?
let arr = [];
arr.push(response.data);
console.log(arr);
I think problem with collection manipulation, When you do collection manipulation, it changes the values.
public function messages() {
$boats = Boat::with('media')->get();
$sorted = $boats->sortByDesc('do your sort here');
return $sorted->values();
}

NodeJS - Map forEach Async/Await

I am working with node. I have an array of ids. I want to filter them based on a response of a call of other API. So i want to populate each id and know if they assert or not the filter i am doing based on the API.
I am using async/await. I found that the best approach is using Promises.all, but this is not working as expected. What i am doing wrong?
static async processCSGOUsers (groupId, parsedData) {
let steamIdsArr = [];
const usersSteamIds = parsedData.memberList.members.steamID64;
const filteredUsers = await Promise.all(usersSteamIds.map(async (userId) => {
return csGoBackpack(userId).then( (response) => {
return response.value > 40;
})
.catch((err) => {
return err;
});
}));
Object.keys(usersSteamIds).forEach(key => {
steamIdsArr.push({
steam_group_id_64: groupId,
steam_id_64: usersSteamIds[key]
});
});
return UsersDao.saveUsers(steamIdsArr);
}
Apart from that, it is happening something weird. When i was debbuging this, data parameters on this method is coming fine. When i reach on the line of the Promise.all i got a "reference error" on each parameter. I do not why.
Wait for all responses, then filter based on the results:
const responses = await Promise.all(usersSteamIds.map(csGoBackpack));
// responses now contains the array of responses for each user ID
// filter the user IDs based on the corresponding result
const filteredUsers = usersSteamIds.filter((_, index) => responses[index].value > 40);
If you don't mind using a module, you can do this kind of stuff in a straightforward way using these utilities

Categories

Resources