How to avoid promise nesting - firebase

I have a feeling I'm doing an overkill nesting in the following simple function, which simply reads a value from one location and writes it to another location.
Is there a way to simplify it somehow?
exports.myFunc = functions.database.ref('...').onCreate(event => {
const list_id = event.params.list_id;
return new Promise(function(resolve, reject){
event.data.adminRef.root.child('lists').child(list_id).child('owner').once('value').then(function(snap){
const owner_id = snap.val();
if (owner_id != null) {
event.data.adminRef.root.child('users').child(owner_id).child('whatever').child('whatever2').set(true)
.then(function() {
resolve();
},
function(err) {
reject();
}
)
} else {
reject();
}
});
});
})

You don't need a new promise if you have existing promises to work with. You can return promises from then to continue chaining.
exports.myFunc = functions.database.ref('...').onCreate(event => {
const list_id = event.params.list_id;
return event.data.adminRef.root.child('...').once('value')
.then(function(snap){
const owner_id = snap.val();
if (owner_id != null) {
return event.data.adminRef.root.child('...').set(true)
} else {
return Promise.reject('error message')
}
});
})

Related

Fetch api in useEffect to make an infinite scroll but execute in wrong order

I am trying to fetch data from Algolia database (index.search is similar to fetch) in useEffect,but then I find the order it execute is not the way I think.I console "queryNews1", "queryNews2", ..."queryNews6" in async function queryNews(), and I think they will sequentially print out in console(see image below). But I find that after queryNews2, it "jump out" of queryNews() but execute the code outside queryNews(), after console.log("5"), it go back to execute "queryNews3".
I guess it's an asychronous issue, so I wrap queryNews() inside an another async function const getData = async () => { await queryNews(keyword); }; and call getData(), but it's still execute in wrong way.Why and does anybody know how to fix that??
Sorry for my bad English!
mobile in the console is writing in articleState.map(() => { console.log("mobile"); return (); });
console results image
const [articleState, setArticles] = useState<ArticleType[]>([]);
useEffect(() => {
console.log("1");
if (windowResized === "large" || windowResized === undefined) return;
let isFetching = false;
let isPaging = true;
let paging = 0;
console.log("2");
async function queryNews(input: string) {
console.log("queryNews1");
isFetching = true;
setIsLoading(true);
setSearchState(true);
setPageOnLoad(true);
console.log("queryNews2");
const resp = await index.search(`${input}`, {
page: paging,
});
console.log("queryNews3");
const hits = resp?.hits as ArticleType[];
setTotalArticle(resp?.nbHits);
console.log("queryNews4");
setArticles((prev) => [...prev, ...hits]);
console.log("queryNews5");
setIsLoading(false);
console.log("queryNews6");
paging = paging + 1;
if (paging === resp?.nbPages) {
isPaging = false;
setScrolling(true);
return;
}
console.log("queryNews7");
isFetching = false;
setSearchState(false);
setPageOnLoad(false);
console.log("queryNews8");
}
console.log("3");
async function scrollHandler(e: WheelEvent) {
if (window.innerHeight + window.scrollY >=
document.body.offsetHeight - 100) {
if (isFetching || !isPaging) return;
console.log("scrollHandler");
getData();
}
}
const getData = async () => {
await queryNews(keyword);
};
getData()
console.log("4");
window.addEventListener("wheel", scrollHandler);
console.log("5");
return () => {
window.removeEventListener("wheel", scrollHandler);
};
}, [keyword, setSearchState, windowResized]);
Thanks to kim3er, that really help.
But simillar situation happened again when I scroll, it console.log("queryNews2"), and then it console "mobile", which means it render the component again, and then go back to queryNews() to finish execute the rest of the function?Why didn't it wait while I already put all the code in
getData().then(() => {
console.log("6");
window.addEventListener("wheel", scrollHandler);
console.log("7");
});
Thanks!!
(stack overflow suddenly said I can't embed image now so I paste an image link instead)
https://imgur.com/a/lDKEzxy
useEffect(() => {
console.log("1");
if (windowResized === "large" || windowResized === undefined) return;
let isFetching = false;
let isPaging = true;
let paging = 0;
console.log("2");
async function queryNews(input: string) {
console.log("queryNews1");
isFetching = true;
setIsLoading(true);
setSearchState(true);
setPageOnLoad(true);
console.log("queryNews2");
const resp = await index.search(`${input}`, {
page: paging,
});
console.log("queryNews3");
const hits = resp?.hits as ArticleType[];
setTotalArticle(resp?.nbHits);
console.log("queryNews4");
setArticles((prev) => [...prev, ...hits]);
console.log("queryNews5");
setIsLoading(false);
console.log("queryNews6");
paging = paging + 1;
if (paging === resp?.nbPages) {
isPaging = false;
setScrolling(true);
return;
}
console.log("queryNews7");
isFetching = false;
setSearchState(false);
setPageOnLoad(false);
console.log("queryNews8");
}
console.log("3");
async function scrollHandler(e: WheelEvent) {
if (
window.innerHeight + window.scrollY >=
document.body.offsetHeight - 100
) {
if (isFetching || !isPaging) return;
console.log("scrollHandler");
getData().then(() => {
console.log("6");
window.addEventListener("wheel", scrollHandler);
console.log("7");
});
}
}
const getData = async () => {
await queryNews(keyword);
};
getData().then(() => {
console.log("4");
window.addEventListener("wheel", scrollHandler);
console.log("5");
});
return () => {
window.removeEventListener("wheel", scrollHandler);
};
}, [keyword, setSearchState, windowResized]);
You're calling getData() without awaiting it. Because of this, it'll run parallel to the top level useEffect code. This isn't an issue, if getData() is the last line of code in the function.
But if you do need that initial getData() to complete before hitting console.log("4");, you could do this:
getData()
.then(() => {
console.log("4");
window.addEventListener("wheel", scrollHandler);
console.log("5");
});
From console.log("4"); will run after the call to getData().
Clarifier on async and .then()
With this function in mind:
const doSomething = async () => {
// Do something async
console.log('during');
});
The following:
const asyncFunc = async () => {
console.log('before');
await doSomething();
console.log('after');
});
Is equivalent to:
const asyncFunc = () => {
console.log('before');
doSomething()
.then(() => {
console.log('after');
});
});
Either will return:
before
during
after
However, if you used:
const syncFunc =() => {
console.log('before');
doSomething();
console.log('after');
});
Becasue I have not awaited doSomething(), I have created a race condition, whereby during and after could be returned in a different order depending on how long it took the async code to complete. Because the syncFunc script will continue running as soon as doSomething has been called (but crucially, has not finished).
So you could get:
before
after
during
Wrapping await queryNews(keyword); in another function called getData() does not make the function synchronous, it just means that you don't have to keep typing in the keyword parameter. You still need to await getData(), in order to ensure completion before continuing.
#kim3er
Thank you for your detailed answer, and sorry for my late response.
According to your explanation about difference between async/await and .then(),
I found that I didn't await getData() in scrollHandler(), so I modified my code again, I call queryNews(keyword) this time instead, but then something weird still exist.
The console.log sequence is right when first load(No1~10 in image below),and then when I scroll, I call scrollHandler(), and inside scrollHandler, await queryNews() is executed, but then again,it console to "queryNews2", and then it "jump out" of queryNews() to render component again I guess, because the word "mobile" in JSX tag is console before "queryNews3"(No13~18 in image below).
I use await queryNews(keyword) in scollHandler() this time, but it still has wrong console sequence.Why?Does it has anything to do with setState inside queryNews()?Because as far as I know, setState will trigger a component re-render?
Thank you for answer my question patiently.
console image
useEffect(() => {
console.log("1");
if (windowResized === "large" || windowResized === undefined) return;
let isFetching = false;
let isPaging = true;
let paging = 0;
console.log("2");
async function queryNews(input: string) {
console.log("queryNews1");
isFetching = true;
setIsLoading(true);
setSearchState(true);
setPageOnLoad(true);
console.log("queryNews2");
const resp = await index.search(`${input}`, {
page: paging,
});
console.log("queryNews3");
const hits = resp?.hits as ArticleType[];
setTotalArticle(resp?.nbHits);
console.log("queryNews4");
setArticles((prev) => [...prev, ...hits]);
console.log("queryNews5");
setIsLoading(false);
console.log("queryNews6");
paging = paging + 1;
if (paging === resp?.nbPages) {
isPaging = false;
setScrolling(true);
return;
}
console.log("queryNews7");
isFetching = false;
setSearchState(false);
setPageOnLoad(false);
console.log("queryNews8");
}
console.log("3");
async function scrollHandler(e: WheelEvent) {
if (
window.innerHeight + window.scrollY >=
document.body.offsetHeight - 100
) {
if (isFetching || !isPaging) return;
console.log("scrollHandler");
await queryNews(keyword);
console.log("end scrollHandler");
}
}
queryNews(keyword).then(() => {
console.log("4");
window.addEventListener("wheel", scrollHandler);
console.log("5");
});
return () => {
window.removeEventListener("wheel", scrollHandler);
};
}, [keyword, setSearchState, windowResized]);

React native cannot set multiple arrayitems from Firebase in loop

I am trying to get data from Firebase realtime database in the loop and set array items,
but just the last item can set.
it's looking like synchronize problems I tried a lot of things but couldn't solve it.
import FireBaseConnection from '../classes/firebaseconnection.js';
const getComments = () => {
let cardatafetch=[]
FireBaseConnection.GetData('/PostComments/1234').then((comments) => {
for (i in comments) {
cardatafetch.push(comment[i])
}
for (j in cardatafetch) {
var UserId = cardatafetch[j]["UserID"]
FireBaseConnection.GetData('/Users/'+UserId).then((user) => {
cardatafetch[j].ProfilePicture=user["ProfilePicture"]
})
.catch((error) => {
console.log(error)
});
}
console.log(cardatafetch)
}).catch((error) => {
console.log(error)
});
}
}
Console Output is
Same problem also during get images from storage
for (j in cardatafetch) {
FireBaseConnection.GetImage().then((obj) => {
cardatafetch[j].ProfilePicture=obj
})
.catch((error) => {
console.log(error)
});
}
FireBaseConnection Class
import database from '#react-native-firebase/database';
import storage from '#react-native-firebase/storage';
import { utils } from '#react-native-firebase/app';
class FireBaseConnection
{
static async GetData(refValue) {
let data;
await database()
.ref(refValue)
.once('value')
.then(snapshot => {
data = snapshot.val();
});
return data;
}
static async GetImage(imgValue) {
const reference = storage().ref(imgValue);
let imagePath= await reference.getDownloadURL().then(result =>result);
return imagePath;
}
}
export default FireBaseConnection;
Try below code, what I have done is put your code inside last iteration of the loop so it will be implemented only once when all the items are pushed in the array.
import FireBaseConnection from '../classes/firebaseconnection.js';
const getComments = () => {
return new Promise((resolve, reject) => {
let commentsArr = [];
FireBaseConnection.GetData('/PostComments/1234').then((comments) => {
Object.keys(comments).forEach((key, index) => {
commentsArr.push(comments[key])
if(index == Object.keys(comments).length-1) {
resolve(commentsArr);
}
});
}).catch((error) => {
console.log(error)
});
});
}
const addImagesToComment = () => {
this.getComments().then((comments) => {
var finalArr = [];
comments.forEach((comment, index) => {
var tempComment = comment;
var UserId = comment["UserID"]
FireBaseConnection.GetData('/Users/' + UserId).then((user) => {
tempComment.ProfilePicture = user["ProfilePicture"]
finalArr.push(tempComment);
}).catch((error) => {
console.log(error)
});
if(index == comments.length-1) {
console.log(finalArr)
}
});
});
}
Try calling getComments function.

Firebase returning undefinfed

I keep getting a response undefined with the return function. After a second the console.log displays the information. My guess is that the data is still being gathered and that the function is already returning. I thought it would be solved with a promise but it has not. What am I missing? why is my function returning without any data?
TakenSpaces = 0
let startDate = new Date(time)
startDate.setHours(0)
startDate.setMinutes(0)
let endDate = new Date(startDate)
endDate.setHours(24)
data = []
const reservations = db.collection('organisation').doc('Amigos').collection('reservations')
.where('start', '>=', startDate)
.where('start', '<=', endDate).get()
console.log('promse made')
reservations.then((docs) => {
// console.log(docs.data())
// return docs.data()
// const promises = []
docs.forEach((doc) => {
data.push(doc.id)
})
// console.log(data)
// console.log('DONE TESTING')
return data
// return Promise.all(promises)
})
.then((test) => { console.log(test); return test })
// for (const reservation of reservations) {
// console.log(reservation)
// }
// .then((docs) => {
// // for (const doc of docs) {
// // console.log(doc.id)
// // }
// docs.forEach((doc) => {
// if (doc.data().people){
// const people = doc.data().people
// TakenSpaces = TakenSpaces + people
// }
// // console.log(doc.data().people)
// });
// return TakenSpaces
// })
// return TakenSpaces
}
const t = getTakenCapacity(time)
console.log(t)
It works know. But If anyone knows if the function can only return when everything is finished and not have to return a promise function.
function getTakenCapacity (time) {
TakenSpaces = 0
let startDate = new Date(time)
startDate.setHours(0)
startDate.setMinutes(0)
let endDate = new Date(startDate)
endDate.setHours(24)
data = []
const reservations = db.collection('organisation').doc('Amigos').collection('reservations')
.where('start', '>=', startDate)
.where('start', '<=', endDate).get()
return reservations.then((docs) => {
// console.log(docs.data())
// return docs.data()
// const promises = []
var dd = new Array
docs.forEach((doc) => {
data.push([[time], doc.data().people])
})
// console.log(data)
// console.log('DONE TESTING')
return data
// return Promise.all(promises)
})
getTakenCapacity(time).then(function(result) {
console.log(result) // "Some User token"
})

alexa sdk: saving sessionAttributes in https response

i have a skill and i want to load some data from an url and store it in the SessionAttributes.
so i wrote this into my handle(handlerInput)of my LaunchRequestHandler:
require('https').get(url, (resp) => {
resp.on('data', (chunk) => {
data += chunk;
});
resp.on('end', () => {
let attributes = JSON.parse(data);
console.log(attributes);
handlerInput.attributesManager.setSessionAttributes(attributes);
});
});
the log shows me the correct object, but when i try to load the sessionAttributes in the next intent it's empty. I Assume it has something to do with the setSessionAttributes being in the response function, because if i set something directly after this code, it works. Any ideas?
This might be because of the asynchronous operation. Please use async/await to make the API call and then save. Sample example,
const getData = () => {
return new Promise((resolve, reject) => {
require("https").get(url, resp => {
resp.on("data", chunk => {
data += chunk;
});
resp.on("end", () => {
resolve(data);
});
});
});
};
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === "LaunchRequest";
},
async handle(handlerInput) {
const speechText = "welcome";
const data = await getData(); //asynchronous operation
const sessionAttributes = handlerInput.attributesManager.getSessionAttributes();
sessionAttributes.data = data;
handlerInput.attributesManager.setSessionAttributes(sessionAttributes);
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.withSimpleCard("Welcome to the Skill", speechText)
.getResponse();
}
};

Cloud Functions for Firebase return array of promises to GCF

In my onWrite event handler I perform data updates in different database paths. The function gets interrupted without finishing performing tasks. I am implementing Promise.all() to send array of promises to GCF and await for all outstanding work/tasks to complete. Console log is not showing errors. I am trying to find out if I am doing the right implementation.
exports.ObserveProposals = functions.database.ref("/proposals/{jobid}/{propid}").onWrite((event) => {
const jobid = event.params.jobid;
const userId = event.params.propid;
const promises = [];
const isinvitation = event.data.child("isinvitation").val();
if (!isinvitation) {
return userRef = admin.database().ref(`users/${userId}/proposals/sent`);
if (event.data.exists() && !event.data.previous.exists()) {
return userRef.child(jobid).set({
timestamp: admin.database.ServerValue.TIMESTAMP
});
} else if (!event.data.exists() && event.data.previous.exists()) {
return userRef.child(jobid).remove();
}
promises.push(userRef);
}
return collectionRef = admin.database().ref(`/jobs/${jobid}`).once('value').then(snapshot => {
if (snapshot.val() !== null) {
return countRef = collectionRef.child("proposals").transaction(current => {
if (event.data.exists() && !event.data.previous.exists()) {
return (current || 0) + 1;
} else if (!event.data.exists() && event.data.previous.exists()) {
return (current || 0) - 1;
}
});
}
promises.push(collectionRef);
});
return Promise.all(promises);
});
Bellow function is working properly, array of promises is being sent to GCF and all tasks are executed.
exports.ObserveProposals = functions.database.ref("/proposals/{jobid}/{propid}").onWrite((event) => {
const jobid = event.params.jobid;
const userid = event.params.propid;
const promises = [];
let userRef = admin.database().ref(`users/${userid}/proposals`);
let jobRef = admin.database().ref(`/jobs/${jobid}/proposals`);
jobRef.once('value').then(snapshot => {
if (snapshot.val() !== null) {
jobRef.transaction(current => {
if (event.data.exists() && !event.data.previous.exists()) {
return (current || 0) + 1;
} else if (!event.data.exists() && event.data.previous.exists()) {
return (current || 0) - 1;
}
});
}
});
promises.push(jobRef);
if (event.data.exists() && !event.data.previous.exists()) {
const isInvitation = event.data.child("isinvitation").val();
if (!isInvitation) {
return userRef.child(`/sent/${jobid}`).set({
timestamp: admin.database.ServerValue.TIMESTAMP
});
} else if (isInvitation) {
return userRef.child(`/received/${jobid}`).set({
timestamp: admin.database.ServerValue.TIMESTAMP
});
}
} else if (!event.data.exists() && event.data.previous.exists()) {
const isInvitation = event.data.previous.child("isinvitation").val();
if (!isInvitation) {
return userRef.child(`/sent/${jobid}`).remove();
}else if (isInvitation) {
return userRef.child(`/received/${jobid}`).remove();
}
}
promises.push(userRef);
return Promise.all(promises);
});

Resources