How to ensure function with promise continues execution - javascript

I have a function in a react-native application that is executed on a button press, it calls an async action, and then resets the navigation stack. the function looks something like this:
confirm = () => {
this.props.addEvent(args);
this.props.loading != true ? this.props.navigation.dispatch(popAction) : null;
}
the addEvent() action looks like this:
export const addEvent = (event, msgId, convoId) => {
return (dispatch) => {
dispatch({ type: types.UPDATING });
console.log('Firestore Write: (actions/agenda) => addEvent()');
return firebase
.firestore()
.collection('events')
.add({
date: event.date,
token: event.token,
withName: event.sender
})
.then((success) => {
updateReqToScheduled(msgId, { uid: event.schedulerId, convoId: convoId });
dispatch({ type: types.EVENT_ADD_SUCCESS });
})
.catch((err) => {
console.log('ERROR => addEvent()' + '\n' + err.message);
dispatch({ type: types.EVENT_ADD_FAIL, info: err.message });
});
};
};
the pre-firebase log statement executes, the document is added, and the updateReqToScheduled function execution begins. it looks like:
const updateReqToScheduled = (id, reader) => {
console.log('Firestore Write: (actions/agenda) => updateReqToScheduled()');
return firebase
.firestore()
.collection('messages')
.doc(id)
.update({ read: true })
.then((success) => {
return updateConvoUnread(reader);
})
.catch((err) => {
console.log(err.message);
});
};
this function also executes completely. the updateConvoUnread() function looks like:
const updateConvoUnread = (reader) => {
console.log('Firestore Read: (actions/agenda) => updateConvoUnread( 1 )');
return firebase
.firestore()
.collection('messages')
.where('userConvos', 'array-contains', reader.convoId)
.where('sender', '!=', reader.uid)
.where('read', '==', false)
.get()
.then((querySnapshot) => {
console.log('PRECONDITION');
if (querySnapshot.empty == true) {
console.log('Firestore Write: (actions/agenda) => updateConvoUnread( 2 )');
return firebase
.firestore()
.collection('user-conversations')
.doc(reader.convoId)
.update({ unread: false });
} else {
console.log('ELSE CLAUSE');
//return;
}
console.log('POST - IF');
})
.catch((err) => {
console.log('ERROR => updateConvoUnread(): ' + err.message);
});
};
the very first pre-firebase log statement executes, but no other log statement executes, not in the then(), not in the catch() and not in the conditional statement, thus the function execution halts and the navigation stack reset isn't executed.
Can anyone advise on the situation?

Use async on your function declarations and await on the firebase calls

Something was wrong with the firebase query. a name of the field was incorrect. it halted everything. once corrected the function finished as expected and the route was reset.

Related

Why is this asynchronous function being called twice

I am trying to create a user with email and password using firebase, but when I call the function that creates it, it is called twice and I get an error because I am trying to register the email that is already in use.
I noticed that the console.log('CALLED') is called once, I don't understand why RegisterWithEmail is called twice. My auth flow only creates the userDocument in the confirmation phase, for this reason userSnap.length equals zero in the second call and tries to create again.
How can I call this function once?
FILE: emailconfirm.page.tsx
registerEmail = async data => {
const { setRegStatus, createDoc } = this.props;
console.log('CALLED')
await RegisterWithEmail(data).then(res => {
console.log('Final response ', res)
if(res === 'EMAIL_VERIFIED') {
createDoc()
setRegStatus({ status: 'created', data: res })
}
else if(res === 'SOMETHING_WENT_WRONG'){
setRegStatus({ status: 'error', data: res })
}
}).catch(err => {
console.log('Error ', err)
setRegStatus({ status: 'error', data: err })
})
}
FILE: firebase.utils.tsx
export const RegisterWithEmail = async user => {
console.log("Called Once...");
if(!user) return 'SOMETHING_WENT_WRONG';
else {
const snap = await firestore.collection('users').where('email', '==', user.email).get();
const docs = snap.docs.map((doc) => doc.data());
if (docs.length !== 0) return 'EMAIL_HAS_ALREADY_BEEN_TAKEN';
try {
console.log("Trying to register email...");
return await auth.createUserWithEmailAndPassword(user.email, user.password).then(async usr => {
await usr.user.updateProfile({
displayName: user.name
}) // SETTING NAME
const sendVerifyEmail = usr.user.sendEmailVerification().then(() => setTimer(usr.user, 5))
return await sendVerifyEmail.then(msg => {
console.log('Finishing...', msg)
if(msg.txt !== 'waiting') {
if(msg.error) {
throw msg.txt
}
else return msg.txt
}
}).catch(() => {
throw 'EMAIL_NOT_SENT'
})
}).catch(() => {
throw 'USER_NOT_CREATED'
})
} catch (err) {
throw 'USER_ALREADY_REGISTERED'
}
}
}
Developer console:
You shouldn't be mixing and matching .then()s in async functions for your own sanity's sake.
Something like
export const RegisterWithEmail = async (user) => {
if (!user) return false;
const snap = await firestore.collection("users").where("email", "==", user.email).get();
const docs = snap.docs.map((doc) => doc.data());
if (docs.length !== 0) return false;
console.log("Trying to register email...");
try {
const resp = await auth.createUserWithEmailAndPassword(user.email, user.password);
// then ...
return true;
} catch (err) {
// catch ...
}
};
might work better for you.
I need more code to be sure, but I think you should add await
registerEmail = async data => {
console.log('CALLED')
await RegisterWithEmail(data)
}

Firebase cloud function to update a record and send a notification

Evening All
Having a few problems performing two actions when a document is created
the below worked until i added the last "then" in the createDocument function where i attempt to send a notification to inform the use via fcm.
exports.createRequest = functions.firestore
.document('requests/{requestId}')
.onCreate((snap, context) => {
var email = snap.data().requestedFromEmail;
checkUserInFirebase(email)
.then((user) => {
//get user profile
return getUserProfile(user.user.uid);
})
.then(userProfile => {
return snap.ref.set({ requestedFromName: userProfile.data().fullName, requestedFromId: userProfile.id }, {merge:true});
})
.then(value=> {
return sendNotification(snap.data().requestedFromId, snap.data().requestedByName);
})
.catch(error => {return error;})
}
)
can anyone see where im going wrong, all the examples im finding send the fcm explicitly from using a exports. Ideally id like to pass the userProfile object through to the send notification function but um not sure how to do that and still set the changes to the document. Full code is below
async function checkUserInFirebase(email) {
return new Promise((resolve) => {
admin.auth().getUserByEmail(email)
.then((user) => {
return resolve({ isError: false, doesExist: true, user });
})
.catch((err) => {
return resolve({ isError: true, err });
});
});
}
async function getUserProfile(uid) {
return admin.firestore()
.collection("users")
.doc(uid)
.get();
}
async function sendNotification(uid, requestedByName) {
const querySnapshot = await db
.collection('users')
.doc(uid)
.collection('tokens')
.get();
const tokens = querySnapshot.docs.map(snap => snap.token);
console.info(tokens);
const payload = {
notification: {
title: 'New Request!',
body: `You received a new request from ${requestedByName}`,
icon: 'your-icon-url',
click_action: 'FLUTTER_NOTIFICATION_CLICK'
}
};
return fcm.sendToDevice(tokens, payload);
}
exports.createRequest = functions.firestore
.document('requests/{requestId}')
.onCreate((snap, context) => {
var email = snap.data().requestedFromEmail;
checkUserInFirebase(email)
.then((user) => {
//get user profile
return getUserProfile(user.user.uid);
})
.then(userProfile => {
return snap.ref.set({ requestedFromName: userProfile.data().fullName, requestedFromId: userProfile.id }, {merge:true});
})
.then(value=> {
return sendNotification(snap.data().requestedFromId, snap.data().requestedByName);
})
.catch(error => {return error;})
}
)
Your funciton needs to return a promise that resolves when all the async work is complete. Right now it returns nothing, which means that Cloud Functions might terminate it up before the work is done. You should return the promise chain:
return checkUserInFirebase(email)
.then((user) => {
//get user profile
return getUserProfile(user.user.uid);
})
.then(userProfile => {
return snap.ref.set({ requestedFromName: userProfile.data().fullName, requestedFromId: userProfile.id }, {merge:true});
})
.then(value=> {
return sendNotification(snap.data().requestedFromId, snap.data().requestedByName);
})
.catch(error => {return error;})
}
Note the return at the start of the whole thing.
See the documentation for more information.

How to wait for a successful async action before changing url?

so I'm using a popup to log my users in with firebase:
const loginToApp = (provider) => {
firebaseApp
.auth()
.signInWithPopup(provider)
.then(async (result) => {
if (result.additionalUserInfo.isNewUser) {
// problem is this line
await setNewUserInformation(result.user.uid)
}
const { user } = result
setUser(user)
// and this line
window.location.href = 'newRoute'
})
.catch((error) => {
console.log('ERROR:', error)
})
}
so if I remove window.location.href = 'visited' this all works fine and it sets in firebase. I'm probably doing something stupid but I cant figure out how to wait for this function to fire setNewUserInformation and to complete before I move to the new page?
function code:
export const setNewUserInformation = (userId) => {
return {
type: 'SET_NEW_USER_INFORMATION',
userId,
}
}
this then has a redux observable epic listening to it:
return action$.pipe(
ofType('SET_NEW_USER_INFORMATION'),
mergeMap((action) => {
return from(
firebaseApp.database().ref(firebaseRef).update(userInformation),
).pipe(
mergeMap(() => {
return [updatedUserInformationSuccess()]
}),
catchError((error) => of(updatedUserInformationFailure(error))),
)
}),
)
setNewUserInformation() is an action creator, which is sync. You do not need to wait for it as it does not return anything useful to you logic. What you need to do, is move window.location.href = 'newRoute' to separate logic, and make it depend on state returned from action creators updatedUserInformationSuccess() and updatedUserInformationFailure(error). If your component is functional, put this logic in a useEffect. If it is a class component, use ComponentDidUpdate lifecycle method.
Use it like below
const loginToApp = (provider) => {
firebaseApp
.auth()
.signInWithPopup(provider)
.then(async (result) => {
new Promise((resolve, reject) => {
if (result.additionalUserInfo.isNewUser) {
// problem is this line
setNewUserInformation(result.user.uid)
}
const { user } = result
resolve(user)
}).then((user)=>{
setUser(user)
// and this line
window.location.href = 'newRoute'
})
})
.catch((error) => {
console.log('ERROR:', error)
})
}
Because on then You can returned a Promise and resolve later. We could re-write the code above like this below:
const loginToApp = (provider) => {
firebaseApp
.auth()
.signInWithPopup(provider)
.then((result) => {
if (result.additionalUserInfo.isNewUser) {
// return for next resolve function
return setNewUserInformation(result.user.uid).then(() => result);
}
return result;
})
.then((result) => {
// after all above promises resolve
const { user } = result
setUser(user)
// and this line
window.location.href = 'newRoute'
})
.catch((error) => {
console.log('ERROR:', error)
})
}
Are you using React?
If yes, then you can simply use didUpdate Cycle to route to new url after successful action dispatched. Move your "window.location.href = 'newRoute'" under the ComponentDidUpdate with props check.

Promise executes then function before previous then execution is completed

I'm try to chain a couple of then functions which execute sequentially, but the last .then() is being executed before the previous is done executing and as a result it sends an empty payload. Following is the snippet:
router.get("/selectedHotels", function(req, res) {
let payload = [];
return collectionRef
.where("isOwner", "==", true)
.get() //fetches owners
.then(snapshot => {
snapshot.forEach(user => {
console.log("User", user);
collectionRef
.doc(user.id)
.collection("venues")
.get() // fetches hotels from owners
.then(snapshot => {
snapshot.forEach(doc => {
if (
doc.data().location.long == req.query.long &&
doc.data().location.lat == req.query.lat
) {
console.log(doc.id, "=>", doc.data());
payload.push({
id: doc.id,
data: doc.data()
});
}
});
})
.catch(err => {
console.log("No hotels of this user", err);
});
});
})
.then(() => {
console.log("Payload", payload);
response(res, 200, "Okay", payload, "Selected hotels");
})
.catch(err => {
console.log("Error getting documents", err);
response(res, 404, "Data not found", null, "No data available");
});
});
Any suggestions? Thanks
Your main mistake is that you have a non-promise returning function, forEach, in the middle of your nested promise chain.
router.get('/selectedHotels',function(req,res){
let payload = [];
return collectionRef.where(...).get()
.then((snapshot)=>{
snapshot.forEach(user => {
// ^^^^^^^^^^^^^^^^^ this means the outer promise doesn't wait for this iteration to finish
// ...
The easiest fix is to map your array of promises, pass them into Promise.all and return them:
router.get('/selectedHotels',function(req,res){
let payload = [];
return collectionRef.where(...).get()
.then((snapshot)=> {
return Promise.all(snapshot.map(
// ...
return collectionRef.doc(user.id).collection('venues').get()
.then(...)
))
That being said, nesting promises like this is an anti-pattern. A promise chain allows us to propagate values through the then callbacks so there's no need to nest them.
Instead, you should chain them vertically.
Here's an example of how you can do that:
router.get("/selectedHotels", function(req, res) {
return collectionRef
.where("isOwner", "==", true)
.get() //fetches owners
// portion of the chain that fetches hotels from owners
// and propagates it further
.then(snapshot =>
Promise.all(
snapshot.map(user =>
collectionRef
.doc(user.id)
.collection("venues")
.get()
)
)
)
// this portion of the chain has the hotels
// it filters them by the req query params
// then propagates the payload array
// (no need for global array)
.then(snapshot =>
snapshot
.filter(
doc =>
doc.data().location.long == req.query.long &&
doc.data().location.lat == req.query.lat
)
.map(doc => ({ id: doc.id, data: doc.data() }))
)
// this part of the chain has the same payload as you intended
.then(payload => {
console.log("Payload", payload);
response(res, 200, "Okay", payload, "Selected hotels");
})
.catch(err => {
console.log("Error getting documents", err);
response(res, 404, "Data not found", null, "No data available");
});
});
Your using firestore so you need to give all documents to map and you also need to return some values to next then. I hope this will help you to solve your problem.
router.get('/selectedVenues',function(req,res){
return collectionRef.where('isOwner', '==', true).get()
.then(snapshot => {
let venues = [];
snapshot.docs.map(user => {
venues.push(collectionRef.doc(user.id).collection('venues').get());
});
return Promise.all(venues);
}).then(snapshots => {
let payload = [];
snapshots.forEach(venues => {
venues.docs
.filter(doc =>
doc.data().longitude == req.query.lng &&
doc.data().latitude == req.query.lat
)
.map(doc =>
payload.push({
id: doc.id,
data: doc.data()
})
)
});
return payload ;
}).then(payload => {
console.log('Payload', payload);
response(res, 200, "Okay", payload, "Selected hotels");
}).catch(err => {
console.log('Error getting documents', err);
response(res, 404, 'Data not found', null, 'No data available');
});
});
You're not returning a promise from within your first then, so there's no way for the code to know that it should wait for an asynchronous result.
router.get('/selectedHotels',function(req,res){
let payload = [];
return collectionRef.where('isOwner', '==', true).get() //fetches owners
.then((snapshot)=>{
var userVenuesPromises = [];
snapshot.forEach(user => {
userVenuesPromises.push(collectionRef.doc(user.id).collection('venues').get());
})
return Promise.all(userVenuesPromises);
})
.then((snapshots) => {
snapshots.forEach((snapshot) => {
snapshot.forEach((doc)=> {
if (doc.data().location.long == req.query.long && doc.data().location.lat == req.query.lat){
console.log(doc.id, '=>', doc.data());
payload.push({
id: doc.id,
data: doc.data()
});
}
});
});
return payload;
})
.then((payload) => {
...
In addition to using Promise.all() to ensure all nested loads are done before continuing to the next step, this also removes the nested promise, instead unpacking the values from the snapshots in a an additional step.
When chaining .then with asynchronous work, you need to return the promise you want to resolve before the next .then is executed. Something like this :
return Promise.all(snapshot.map(user => {
console.log("User", user);
return collectionRef.doc(user.id).collection('venues').get() // fetches hotels from owners
.then(snapshot => {
snapshot.forEach((doc)=> {
if (doc.data().location.long == req.query.long && doc.data().location.lat == req.query.lat){
console.log(doc.id, '=>', doc.data());
payload.push({
id: doc.id,
data: doc.data()
});
}
});
}).catch((err)=>{
console.log('No hotels of this user', err);
});
});
)
You can see it in action in this sample snippet :
function asyncStuff() {
return new Promise(resolve => {
setTimeout(() => {
console.log('async')
resolve();
}, 100)
});
}
function doStuff() {
console.log('started');
asyncStuff()
.then(() => {
return Promise.all([0,1,2].map(() => asyncStuff()));
})
.then(() => {
console.log('second then');
})
.then(() => console.log('finished'));
}
doStuff();
And see that without the return it gives your initial behaviour :
function asyncStuff() {
return new Promise(resolve => {
setTimeout(() => {
console.log('async')
resolve();
}, 100)
});
}
function doStuff() {
console.log('started');
asyncStuff()
.then(() => {
Promise.all([0,1,2].map(() => asyncStuff()));
})
.then(() => {
console.log('second then');
})
.then(() => console.log('finished'));
}
doStuff();

How to access a variable from within an inner function in react/redux actions?

Within my getUnaddedCartItems() function I call loadCartItems() but I need access to the const addedItems within the .then() function. This all happens within my cart actions js file. Can someone please tell me how I can have access to an outer variable within an inner function?
export function loadCartItems() {
return (dispatch, getState) => {
dispatch({
type: types.LOAD_CART_PRODUCTS
});
return AsyncStorage.getItem(STORAGE_KEY_JWT_TOKEN).then((key) => {
return API.getCartItems(key)
.then((response) => {
return dispatch({
type: types.LOAD_CART_PRODUCTS_SUCCESS,
response
});
}).catch(err => {
console.log('Error retrieving cart products');
})
}).catch(err => {
console.log("Error retrieving cart items from local storage");
});
};
}
export function getUnaddedCartItems() {
return (dispatch, getState) => {
dispatch({
type: types.GET_UNADDED_ITEMS
});
return AsyncStorage.getItem(STORAGE_KEY_CART_ITEMS).then((result) => {
const addedItems = JSON.parse(result);
loadCartItems()(dispatch, getState).then((result) => {
const cartItems = result.response.products;
const unaddedCartItems = addedItems.filter((addedItem) => {
return cartItems.find(cartItem => cartItem.id !== addedItem.productId);
});
return unaddedCartItems;
}).catch(err => {
console.log('error: ', err);
});
}).catch(error => {
console.log('error: ', error);
});
};
}
Try removing the return keyword at the beginning of the line
return AsyncStorage.getItem(STORAGE_KEY_CART_ITEMS).then((result) => {
That might be preventing your inner function from running, if it ends the execution too early.
Also, I see you call
dispatch({
type: types.GET_UNADDED_ITEMS
});
at the beginning of the function,
but nowhere else are you dispatching an action afterwards. I think you need dispatch the appropriate action instead of return unaddedCartItems at the end of the inner then()
Update
Replacing
loadCartItems()(dispatch, getState).then((result) => {
const cartItems = result.response.products;
const unaddedCartItems = addedItems.filter((addedItem) => {
return cartItems.find(cartItem => cartItem.id !== addedItem.productId);
});
return unaddedCartItems;
}).catch(err => {
console.log('error: ', err);
});
With dispatch(loadCartItems()) may help achieve the desired outcome. Calling it without dispatch does not connect it to the redux store, which you are using inside the inner .then of loadCartItems

Categories

Resources