data returned by YouTube API seems immutable? - javascript

onSearch = async () => {
const query = qs.stringify({ ...API_QUERY_PARAMS, q: this.state.searchString });
const url = `https://www.googleapis.com/youtube/v3/search?${query}`
const { data } = await axios.get(url);
data.items.forEach(async vid => {
let id = vid.id.videoId; //Individual video ID
const individualQuery = qs.stringify({ ...INDIVIDUAL_API_QUERY_PARAMS, id });
const individualURL = `https://www.googleapis.com/youtube/v3/videos?${individualQuery}`;
const { data } = await axios.get(individualURL);
//data.items[0].statistics does give me the object that I want
vid['statistics'] = data.items[0].statistics
})
this.setState({ videos: data.items });
console.log(this.state.videos);
}
Basically the above onSearch method will call YouTube API and return me a list of videos, in data.items
For each and every video/item, they are lacking of statistics and so I'm firing another call to retrieve the data, the data successfully returned as data.items[0].statistics, I was thinking then to append into individual item as a property.
No exception being thrown, however I don't see the newly created statistics property too. The idea is like below in a very much simpler form.
let items = [
{id: '123', title: 'John'},
{id: '123', title:'sammy'}
]
items.forEach(x=> {
x['statistics'] = { propA: 'A', propB: 'B'};
})
console.log(items);

Putting an async function inside a forEach won't pause the outer thread until all iterations have been completed - you need Promise.all to map each asynchronous iteration to a Promise and wait for each Promise to be resolved before continuing instead:
const query = qs.stringify({ ...API_QUERY_PARAMS, q: this.state.searchString });
const url = `https://www.googleapis.com/youtube/v3/search?${query}`
const { data } = await axios.get(url);
await Promise.all(data.items.map(async (vid) => {
let id = vid.id.videoId; //Individual video ID
const individualQuery = qs.stringify({ ...INDIVIDUAL_API_QUERY_PARAMS, id });
const individualURL = `https://www.googleapis.com/youtube/v3/videos?${individualQuery}`;
const { data } = await axios.get(individualURL);
vid.statistics = data.items[0].statistics
}))
this.setState({ videos: data.items });

Related

Successive queries to firebase storage only fulfills the first call

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();

How to await admin.auth().getUser() method within a forEach loop?

I am trying to iterate over an array of comments and need to grab the commenter's uid for each comment. I am a beginner to JavaScript and need a little bit of help with the following use case:
I need to grab the uid for each comment and then run a .getUser() method which will return the user's email address that is associated with the user's uid. Since .getUser() returns a promise (method reference link), I need to await somewhere in this loop. How to do so? Is this even a good approach?
(Note: My end goal is to eventually attach the email addresses to a to property in a msg object where I will then send out email notifications.)
Example data for comments:
[
{
id: 1,
uid: 'RGaBbiui'
},
{
id: 2,
uid: 'ladfasdflal'
},
{
id: 3,
uid: 'RGaBbiui'
},
{
id: 4,
uid: 'RGaBbiui'
},
{
id: 5,
uid: 'ladfasdflal'
},
{
id: 6,
uid: 'ladfasdflal'
}
]
Cloud function example:
export const sendCommentNotification = functions.firestore
.document('users/{uid}/posts/{postId}/comments/{commentId}')
.onCreate(async (snapshot, context) => {
try {
const commentsQuery = await admin
.firestore()
.collection(
`users/${context.params.uid}/posts/${context.params.postId}/comments`
)
.get()
const commentsArr = []
commentsQuery.forEach((documentSnapshot) =>
commentsArr.push(documentSnapshot.data())
)
const commentsArrUids = new Set(commentsArr.map((c) => c.uid))
console.log(commentsArrUids)
const emailAddresses = []
commentsArrUids.forEach((uid) =>
emailAddresses.push(admin.auth().getUser(uid)) // how to use await here?
)
...
const msg = {
to: //TO DO..put email addresses here..
...
You cannot use await in a forEach loop. You could use await in a for-loop but they won't run simultaneously as in Promise.all().
You can just await all the promises at once using Promise.all():
Returned values will be in order of the Promises passed, regardless of completion order.
const emailAddresses = []
commentsArrUids.forEach((uid) => {
emailAddresses.push(admin.auth().getUser(uid))
})
const data = await Promise.all(emailAddresses)
Data will be an array of UserRecord.
Then you can use the .map() method to get an array of all the emails.
const emails = data.map((user) => user.email)
The code can be written like this to make it easier:
const commentsDocs = await admin.firestore().collection(`users/${context.params.uid}/posts/${context.params.postId}/comments`).get()
const userIds = commentsDocs.docs.map(comment => comment.userId)
const usersReq = userIds.map(u => admin.auth().getUser(u.uid))
const emails = (await Promise.all(usersReq))).map((user) => user.email)
Use for loop instead.
for (let i = 0; i < commentsArrUids.length; i++) {
let user = await new Promise((resolve, reject) => {
admin.auth().getUser(commentsArrUids[i]).then((user) => {
resolve(user);
});
};
emailAddresses.push(user);
}
I will replace forEach with for of and the promises is in series. Also, I rewrite some of your codes as they are redundant.
export const sendCommentNotification = functions.firestore
.document("users/{uid}/posts/{postId}/comments/{commentId}")
.onCreate(async (snapshot, context) => {
try {
const comments = await admin
.firestore()
.collection(
`users/${context.params.uid}/posts/${context.params.postId}/comments`
)
.get();
const uids = new Set();
for (const comment of comments) {
uids.add(comment.data().uid);
}
const emailAddresses = [];
for (const uid of uids) {
const res = await admin.auth().getUser(uid);
emailAddresses.push(res);
}
} catch (err) {
console.log(err);
}
});

Selecting specific field in array of objects returns undefined but logging whole returning whole array gives ok values

I'm trying to get podcast data with `SpotifyAPI. I am fetching the data on my node.js server and sending it as a json to client. I receive a typed object and trying to push this object to an array as I want to have an array of 50 podcasts each of them being an object:
export interface Episode {
name: string;
description: string;
date: string;
img: string;
url: string
};
I'm sending token (to whole function component) and ids of shows I want to fetch
const fetchEpisodesData = async (ids: string[]) => {
let arr: any[] = [];
ids.forEach(async (id, index) => {
const response = await fetch(
`http://localhost:8080/podcastEpisodes?access_token=${tokenCode}&id=${id}`
)
const dataResponse = await response.json();
arr.push(dataResponse)
// )
});
console.log(arr);
};
When I console.log(arr) it shows what I pretty much want and an array of objects but when I console.log(arr[1]) it returns undefined.
Any ideas?
Here is my code of the fetchinng function
export const useFetch = (tokenCode: string) => {
// console.log(tokenCode);
let array: any[] = [];
const [data, setData]: any = useState([]);
const [loading, setLoading] = useState(true);
const getData = async (id: string) => {
const response = await fetch(
`http://localhost:8080/podcastEpisodes?access_token=${tokenCode}&id=${id}`
);
const dataResponse = await response.json();
return dataResponse;
};
const fetchdata = async () => {
const response = await fetch(
`http://localhost:8080/podcast?access_token=${tokenCode}`
);
const dataResponse = await response.json();
// const item = dataResponse.results[0];
const myData = Object.keys(dataResponse).map((key) => {
return dataResponse[key];
});
// console.log(myData[0].show.id)
const idData = myData.map((data) => {
return data.show.id as string;
});
// console.log(idData, 'idData')
return idData;
// console.log(dataResponse);
};
const fetchEpisodesData = async (ids: string[]) => {
let arr: any[] = [];
ids.forEach(async (id, index) => {
const response = await fetch(
`http://localhost:8080/podcastEpisodes?access_token=${tokenCode}&id=${id}`
)
const dataResponse = await response.json();
arr.push(dataResponse)
// )
});
console.log(arr);
};
useEffect(() => {
fetchdata().then((res) => {
// console.log(res);
fetchEpisodesData(res);
});
}, [tokenCode]);
return { data, loading };
};
The issue here is a bit subtle.
You are pushing only one element, the dataResponse object, to your array arr here:
arr.push(dataResponse)
Since you don't push any other value, and arrays in Javascript are 0-indexed, you will only find your value in arr[0], but arr[1] will return undefined.
Now, if you wanted to fill your array arr with an array that would be getting in dataResponse, then you should spread the array when doing .push(), so you'll be pushing all elements found in such dataResponse array object.
Like so:
const dataResponse = await response.json();
// this works only **if** dataResponse is an array
// and you want to add all elements to your 'arr' array
arr.push(...dataResponse);

Unable to await Axios call multiple times

Can someone explain to me why I am unable to perform consecutive axios API calls this way? I figured that since each call was awaited, then both calls would simply be treated the same way.
When I run this code, I get the following error:
TypeError: parsedEmp is not iterable at Object.listEmployees
I do not receive the same error for the Axios call above it. Here's my code:
async function listEmployees() {
const { data } = await axios.get('https://gist.githubusercontent.com/graffixnyc/febcdd2ca91ddc685c163158ee126b4f/raw/c9494f59261f655a24019d3b94dab4db9346da6e/work.json')
const parsedData = data
emp_data = []
for(comp of parsedData) {
emp_data.push([comp['company_name'],comp['employees']])
}
const { data2 } = await axios.get('https://gist.githubusercontent.com/graffixnyc/31e9ef8b7d7caa742f56dc5f5649a57f/raw/43356c676c2cdc81f81ca77b2b7f7c5105b53d7f/people.json')
const parsedEmp = data2
rosters = []
for(entry of parsedEmp) {
if(entry['id'] == e) {
company.employees.push({ first_name: entry['first_name'], last_name: entry['last_name']})
}
}
return rosters
}
Using destructuring assignment requires you to use the property names present in the source object, otherwise the resulting variables will be undefined. The Axios response object does not have a data2 property.
You would need something like this to access the data property
const response2 = await axios.get('https://...')
const parsedEmp = response2.data
or you can use this format to name the assigned variable
const { data: data2 } = await axios.get(...)
const parsedEmp = data2
or even this to save an extra line each time
const { data: parsedData } = await axios.get(/* first URL */)
// ...
const { data: parsedEmp } = await axios.get(/* second URL */)
You can have the code like below
async function listEmployees() {
const { data: parsedData } = await axios.get('https://gist.githubusercontent.com/graffixnyc/febcdd2ca91ddc685c163158ee126b4f/raw/c9494f59261f655a24019d3b94dab4db9346da6e/work.json');
const { data: parsedEmp } = await axios.get('https://gist.githubusercontent.com/graffixnyc/31e9ef8b7d7caa742f56dc5f5649a57f/raw/43356c676c2cdc81f81ca77b2b7f7c5105b53d7f/people.json');
// get the employee details
let emp_data = [];
_.map(parsedData, (data) => {
emp_data.push(data.company_name, data.employees);
});
// get the roster data
let rosters = [];
_.map(parsedEmp, (data) => {
roster.push({ first_name: data['first_name'], last_name: data['last_name']});
});
return rosters;
}

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

Categories