variable outside foreach is null but has a value inside it - javascript

I have this reactjs function to get data from firebase.
The problem is the postsArray. It inside foreach has the objects, outside it is null.
I add postsArray globally.
Any ideas why the return is null?
postsRef.on('value', function(snapshot) {
setPosts(prevPosts => {
snapshot.forEach((subChild) => {
var value = subChild.val();
value = value.postID;
var post = firebase.database().ref('/posts/' + value);
post.on('value', function(snapshot2) {
postsArray = snapshot2.val();
console.log(postsArray); // HAS THE VALUE
});
console.log(postsArray); // NO VALUE HERE.
});
return [...prevPosts, ...Object.keys(postsArray).reverse().map(key => ({
key: key, ...postsArray[key]
}))];

post.on is async function that is why the value outside of the forEach loop is undefined for postArray
The solution here is to set state inside post.on. This however will lead to multiple setState and also keep adding the postArray data state.
postsRef.on('value', function(snapshot) {
snapshot.forEach((subChild) => {
var value = subChild.val();
value = value.postID;
var post = firebase.database().ref('/posts/' + value);
post.on('value', function(snapshot2) {
postsArray = snapshot2.val();
setPosts(prevPosts => {
return [...prevPosts, ...Object.keys(postsArray).reverse().map(key => ({
key: key, ...postsArray[key]
}))];
})
});
})
})
A better solution here is to convert your callback syntax to use promise. I am not sure if firebase.database provides a promise format so I will just show you the traditional way using Promise.all and new Promise
postsRef.on('value', async function(snapshot) {
const promises = []
snapshot.forEach((subChild) => {
var value = subChild.val();
value = value.postID;
var post = firebase.database().ref('/posts/' + value);
promises.push(new Promise((res, rej) => {
post.on('value', function(snapshot2) {
res(snapshot2.val())
});
}));
})
const postArray = await Promise.all(promises);
setPosts(prevPosts => {
return [
...prevPosts,
...Object.keys(postsArray).reverse().map(key => ({
key: key, ...postsArray[key]
}))
];
})
})

post.on() is an asynchronous event listener, your current code is synchronous. If you post more of your code I can help you restructure it.
What is happening is:
var post = firebase.database().ref('/posts/' + value);
var postFunction = function(snapshot2) {
postsArray = snapshot2.val();
console.log(postsArray); // HAS THE VALUE
};
// set the post on function. This isn't calling the function yet.
post.on('value', postFunction);
// No value here because the post function has not run yet.
console.log(postsArray); // NO VALUE HERE.
// the postFunction post.on is called so now you will get the console.
I would change it to look like this:
const addNewPostsToPrevious = (prevPosts) => (newPosts) => {
return [
...prevPosts,
...Object.keys(newPosts)
.reverse()
.map((key) => ({
key,
...newPosts[key],
})),
];
};
postsRef.on('value', (snapshot) => {
setPosts((prevPosts) => {
snapshot.forEach((subChild) => {
const post = firebase.database().ref(`/posts/${subChild.val().postID}`);
post.on('value', (snapshot2) => {
addNewPostsToPrevious(prevPosts)(snapshot2.val());
});
});
});
});

Related

Assigned returned value from function chain into a variable in javascript

Hello I'm having a little problem when I try to assign returned value from function into a variable. I've tried this code with a console.log and it displays the right result but when I want to assign this result to a variable it gives undefined value. So here is the code and can u explain it to me what am I doing wrong because I'm a javascript noobie.
const onDataChange = (items) => {
let products = [];
let images = listFromStorage();
//a function call that displays undefined value but should return array
console.log(images);
items.forEach((item) => {
let key = item.key;
let data = item.val();
products.push({
key : key,
title : data.title,
before : data.before,
after : data.after
})
})
setProductList(...productList, products);
}
const listFromStorage = () => {
let storageRef = firebaseService.getStorage().ref().child('/posts/products');
let images = [];
storageRef.listAll().then(function (res) {
res.items.forEach((imageRef) => {
imageRef.getDownloadURL().then((url) => {
images.push({
url : url
});
});
});
return images;
})
.catch(function (error) {
console.log(error);
});
}
You need to not only wait for the asynchronous code to finish, but you need to also return a value from listFromStorage to assign.
const onDataChange = async (items) => { // <-- declare async
const products = [];
const images = await listFromStorage(); // <-- await result
console.log(images);
items.forEach((item) => {
const key = item.key;
const data = item.val();
products.push({
key: key,
title: data.title,
before: data.before,
after: data.after
})
})
setProductList(...productList, products);
}
const listFromStorage = () => {
const storageRef = firebaseService
.getStorage()
.ref()
.child('/posts/products');
const images = [];
return storageRef // <-- return Promise chain
.listAll()
.then(function (res) {
res.items.forEach((imageRef) => {
imageRef.getDownloadURL().then((url) => {
images.push({ url });
});
});
return images;
})
.catch(function (error) {
console.log(error);
});
}

Promise inside a loop inside an async function

I am working on a project using react and firebase and redux and I have some items that did created by a user. I'm storing the id of the user in the item object so i can populate the user later when i get the item to display.
Now I'm trying to get the items and modify them by replacing the user id with the actual info about the user but I have a promises problem. In my code I just get an empty array which mean the modification didn't get resolved before I return the final result.
export const getItems = () => {
return (dispatch, getState, { getFirebase }) => {
const firestore = getFirebase().firestore();
const items = [];
const dbRef = firestore.collection('items').orderBy('createdAt', 'desc').limit(2);
return dbRef
.get()
.then((res) => {
const firstVisible = res.docs[0];
const lastVisible = res.docs[res.docs.length - 1];
async function getData(res) {
/////////////////////////////////////////////// how to finish this code befor jumping to the return line
await res.forEach((doc) => {
firestore
.collection('users')
.doc(doc.data().owner)
.get()
.then((res) => {
items.push({ ...doc.data(), owner: res.data() });
});
});
////////////////////////////////////////////////
return { docs: items, lastVisible, firstVisible };
}
return getData(res);
})
.catch((err) => {
console.log(err);
});
};
};
I don't get exactly what you are trying to do, but I would suggest putting some order to make your code easy to read and work with.
You can use for of to manage async looping. I suggest something like this, disclaimer, I did it at the eye, problably there are some errors, but you can get the idea.
const getAllDocs = function (data) {
let temp = [];
data.forEach(function (doc) {
temp.push(doc.data());
});
return { data: temp };
};
const getDoc = snap => (snap.exists ? { data: snap.data() } : {});
export const getItems = () => {
return async (dispatch, getState, { getFirebase }) => {
const firestore = getFirebase().firestore();
const dbRef = firestore.collection('items').orderBy('createdAt', 'desc').limit(2);
const usersRef = firestore.collection('users');
let temps = [];
const { data: items } = await dbRef.get().then(getAllDocs);
const firstVisible = items[0];
const lastVisible = items[items.length - 1];
for (const item of items) {
const { data: user } = await usersRef.doc(item.owner).get().then(getDoc);
const owner = {
/* whatever this means*/
};
temps.push({ ...user, owner });
}
return { docs: temps, lastVisible, firstVisible };
};
};
The problem is that an array of Promises is not itself a Promise -- so awaiting it will be a no-op.
You can solve this using Promise.all if you want to load them all asynchronously.
const items = await Promise.all(res.map(async (doc) => {
const res = await firestore.collection('users').doc(doc.data().owner).get();
return { ...doc.data(), owner: res.data() };
});
Otherwise you can await in a for loop as suggested in other answers.

empty array outside nested resolve promise

im tring to push the return value of the resolve to a variable catWithItems which is outside the resolve. inside the resolve the catWithItems works as expected but when i console log catWithItems outside the loop it returns an empty array.
function categoriesSearch(req, res, next) {
let categories = req.batch_categories;
let catWithItems = [];
_.forEach(categories, (category) => {
return new Promise(resolve => {
pos.categoriesSearch(req.tenant, category.id)
.then(item => {
if(item) category.items = item[0];
return category;
})
.then(category => {
catWithItems.push(category);
console.log(catWithItems); //this is works inside here
return resolve(catWithItems);
});
});
});
console.log(catWithItems); //doesn't work returns empty array
res.json({categoryWithItems: catWithItems });
}
this is the pos.categoriesSearch module. it makes a api call to square.(this works as expected)
function categoriesSearch(tenant, category) {
let search_items_url = ${tenant.square.api.v2}/catalog/search,
apiKey = tenant.square.api.key,
payload = {
"object_types": ["ITEM"],
"query": {
"prefix_query": {
"attribute_name": "category_id",
"attribute_prefix": category
}
},
"search_max_page_limit": 1
},
conf = config(search_items_url, apiKey, payload);
return request.postAsync(conf)
.then(items => {
return items.body.objects;
});
}
Your not handling promises right. Try it this way.
function categoriesSearch(req, res, next) {
let categories = req.batch_categories;
let promiseArray = []; // create an array to throw your promises in
let catWithItems = [];
categories.map((category) => {
let promise = new Promise(resolve => {
pos.categoriesSearch(req.tenant, category.id)
.then(item => {
if(item) category.items = item[0];
return category;
})
.then(category => {
catWithItems.push(category);
console.log(catWithItems); //this is works inside here
return resolve(catWithItems);
});
});
promiseArray.push(promise) // add promises to array
});
// resolve all promises in parallel
Promise.all(promiseArray).then((resolved) => {
console.log(resolved);
res.json({categoryWithItems: catWithItems });
})
}
It should be much easier. Not sure if it works, but something to start with:
function categoriesSearch(req, res) {
const categoryWithItems$ = req.batch_categories.map(category =>
pos.categoriesSearch(req.tenant, category.id)
.then(item => ({ ...category, items: item[0] })
);
Promise.all(categoryWithItems$)
.then(categoryWithItems => res.json({ categoryWithItems });
}

Javascript. Wait for nested fetch promises

So i need to use the result of a promise to make another fectch request, i'm working with the rest api for wordpress, and i need the id of subcategories inside the post object to retrieve that category name and build two arrays one with the posts and another with categories names availables.
Here is my function
function fetchAccordionData()
{
const id = document.querySelector('.acc').getAttribute('data-id'),
wpRestAPI = '/wp-json/wp/v2/';
return fetch(wpRestAPI + 'posts?per_page=100&categories=' + id)
.then((resp) => resp.json())
.then((data) =>
{
let curId = [], subCatList = [];
data.map((post) =>
{
let catList = post.categories.filter((c) => c !== parseInt(id));
fetch(wpRestAPI + 'categories/' + catList[0])
.then((r) => r.json())
.then((cat) =>
{
if(!curId.includes(cat.id)) subCatList.push({id: cat.id, name: cat.name});
curId.push(cat.id);
});
});
return {'subCatList':subCatList, 'posts':data}
});
}
Now when i call the function the subCatListarray isn`t ready yet:
fetchAccordionData().then((data) =>
{
console.log(data.subCatList, data.posts);
for(let cat of data.subCatList)
{
console.log(cat);
}
});
So, how do i know when the promise of the second fetch is resolved so i can use the data?
You'll need to place all of your promises in an array and use Promise.all to wait for all of those promises to resolve before accessing subCatList.
Your modified code would look like so:
function fetchAccordionData() {
const id = document.querySelector('.acc').getAttribute('data-id'),
wpRestAPI = '/wp-json/wp/v2/';
return fetch(wpRestAPI + 'posts?per_page=100&categories=' + id)
.then((resp) => resp.json())
.then((data) => {
let curId = [], subCatList = [];
// promises is an array of promises
let promises = data.map((post) => {
let catList = post.categories.filter((c) => c !== parseInt(id));
// return a promise on each iteration
return fetch(wpRestAPI + 'categories/' + catList[0])
.then((r) => r.json())
.then((cat) =>
{
if(!curId.includes(cat.id)) subCatList.push({id: cat.id, name: cat.name});
curId.push(cat.id);
});
});
return Promise.all(promises)
.then(() => ({'subCatList':subCatList, 'posts':data}));
});
}
Notice that the last step returns the object {'subCatList': subCatList, 'post': data} only after every promise in promises has resolved. That way, you can be confident that the promises in the array are finished making their push into subCatList.
It's also worth noting that the interface of fetchAccordionData stayed exactly the same, so you should be able to use it as you did in your original example:
fetchAccordionData().then((data) => {
console.log(data.subCatList, data.posts);
for(let cat of data.subCatList) {
console.log(cat);
}
});
It looks like
return {'subCatList':subCatList, 'posts':data}
is outside of the part of your function that gets categories from the rest API:
data.map((post) =>
{
let catList = post.categories.filter((c) => c !== parseInt(id));
fetch(wpRestAPI + 'categories/' + catList[0])
.then((r) => r.json())
.then((cat) =>
{
if(!curId.includes(cat.id)) subCatList.push({id: cat.id, name: cat.name});
curId.push(cat.id);
});
});
so your function is returning it before it can fetch the category data from the API. If you put your return statement after the last then statement it should return the data your are looking for:
data.map((post) =>
{
let catList = post.categories.filter((c) => c !== parseInt(id));
fetch(wpRestAPI + 'categories/' + catList[0])
.then((r) => r.json())
.then((cat) =>
{
if(!curId.includes(cat.id)) subCatList.push({id: cat.id, name: cat.name});
curId.push(cat.id);
return {'subCatList':subCatList, 'posts':data}
});
});

How to return promise?

I have a function
parseJobs(userId: string) {
this.getLikedJobs(userId).subscribe(result => {
result.map(key =>{
let rows = {
name : (key as any).jobsUser.firstName,
jobType: 'Liked'
}
let job = {...rows,...(key as any).jobPosting};
this.result.push(job);
});
});
this.getSavedJobs(userId).subscribe(result => {
result.map(key =>{
let rows = {
name : (key as any).jobsUser.firstName,
jobType: 'Saved'
}
let job = {...rows,...(key as any).jobPosting};
this.result.push(job);
});
});
return this.result;
}
How to return the result to promise, I tried my best, But I don't know to do t, Maybe its because of two observable I have inside of it,
You would promisify both observables, and then use Promise.all to get a promise that fulfils when all is done:
parseJobs(userId: string) {
// Create a promise
const p1 = new Promise(resolve => {
this.getLikedJobs(userId).subscribe(result => {
// Resolve with the modified array
resolve(result.map(key =>{
let rows = {
name : (key as any).jobsUser.firstName,
jobType: 'Liked'
}
let job = {...rows,...(key as any).jobPosting};
// In a map, you want to return:
return job;
}));
});
});
// Same here:
const p2 = new Promise(resolve => {
this.getSavedJobs(userId).subscribe(result => {
resolve(result.map(key =>{
let rows = {
name : (key as any).jobsUser.firstName,
jobType: 'Saved'
}
let job = {...rows,...(key as any).jobPosting};
return job;
}));
});
});
// Return a promise that will fulfill when both promises fulfill
// and concatenate the results
return Promise.all([p1, p2]).then(result => [].concat(...result));
}
Now you don't store the result in this.result, but make it the promised value, which you get like this:
parseJobs(1).then(result =>
console.log(result);
});
You could of course still store the result in this.result, but that would not be best practice as it suggests that a piece of code may try to access it before it is available: you would always use the then method to get to the result.
Maybe you need something like that:
parseJobs(userId: string): Promise<any> {
let res: Function;
const resPromise = new Promise((resolve: Function) => {
// passing resolve function to upper scope
res = resolve;
});
this.getLikedJobs(userId).subscribe(result => {
...
// resolving result promise
res(result);
});
...
// unresolved
return resPromise;
}
You have 2 async calls. So there is also a single promise solution based on this knowledge.
parseJobs(userId: string) =>
new Promise(resolve => {
let result = [];
const done = (job) => {
result.push(job);
if(result.length === 2) {
resolve(result);
}
}
this.getLikedJobs(userId).subscribe(result => {
result.map(key =>{
let rows = {
name : (key as any).jobsUser.firstName,
jobType: 'Liked'
}
let job = {...rows,...(key as any).jobPosting};
done(job);
});
});
this.getSavedJobs(userId).subscribe(result => {
result.map(key =>{
let rows = {
name : (key as any).jobsUser.firstName,
jobType: 'Saved'
}
let job = {...rows,...(key as any).jobPosting};
done(job);
});
});
});
Also you may look at Promise.all method.

Categories

Resources