Nested .then in javascript for firebase operation - javascript

I have 2 functions like this:
export function senderMessageInbox(senderUID, recieverUID,
recieverProfileURL, recieverUsername) {
return new Promise(function(resolve, reject){
resolve(
firebase.firestore().collection('messages')
.doc(senderUID)
.collection("inboxMessages")
.doc(recieverUID)
.set({
date: new Date().getTime(),
avatarUrl: recieverProfileURL,
message: "",
userId: recieverUID,
username: recieverUsername
})
)
})
}
export function recieverMessageInbox(senderUID, recieverUID,
senderProfileURL, senderUsername) {
return new Promise(function(resolve, reject){
resolve(
firebase.firestore().collection('messages')
Same logic as above, but passing diff variables
)
})
}
I trying to call these in order but need both to complete separately before navigating to another screen. I have something like this:
function startChat() {
console.log("senderMessageInbox");
senderMessageInbox(senderUID, user.uid, user.profileURL, user.username)
.then(() => {
console.log("recieverMessageInbox");
recieverMessageInbox(user.uid, senderUID, currentUser.profileURL, currentUser.username)
})
.then(() => {
console.log("navigation.navigate");
navigation.navigate('Messages')
})
}
Im logging the steps but, it seems like these are overlapping each other and causing overlaps in the db. Is this the correct way? I need these to finish in that order and then navigate to the screen only once they have been completed.

The problem here is that you have the navigation happening asynchronously with the call to receiverMessageInbox. If you need them to happen sequentially, then you must structure the Promise execution accordingly.
function startChat() {
console.log('senderMessageInbox')
senderMessageInbox(senderUID, user.uid, user.profileURL, user.username)
.then(() => {
console.log('receiverMessageInbox')
receiverMessageInbox(user.uid, senderUID, currentUser.profileURL, currentUser.username)
.then(() => {
console.log('navigation.navigate')
navigation.navigate('Message')
})
})
}
Alternatively, you can use async/await to make this less visually confusing:
async function startChat() {
console.log('senderMessageInbox')
await senderMessageInbox(senderUID, user.uid, user.profileURL, user.username)
console.log('receiverMessageInbox')
await receiverMessageInbox(user.uid, senderUID, currentUser.profileURL, currentUser.username)
console.log('navigation.navigate')
await navigation.navigate('Message')
}

You're leaving out some important code where you say firebase logic here, but I'm going to assume you're doing an async call of some sort there.
In this case, you have your resolve() statements in the wrong place. Your resolve is just returning another Promise, which is what the Firebase call will return.
Instead, call resolve() inside the Firebase call once it has returned and you have your data/have completed your updates.
Secondly, in your then chain, you need to return recieverMessageInbox instead of just calling it. So:
return recieverMessageInbox(user.uid, senderUID, currentUser.profileURL, currentUser.username)
Update, based on your added code
There's really not anything the outer Promise is doing, so you could just write your functions like this:
export function senderMessageInbox(senderUID, recieverUID,
recieverProfileURL, recieverUsername) {
return firebase.firestore().collection('messages')
.doc(senderUID)
.collection("inboxMessages")
.doc(recieverUID)
.set({
date: new Date().getTime(),
avatarUrl: recieverProfileURL,
message: "",
userId: recieverUID,
username: recieverUsername
})
}
And apply the same technique to your other function.
And then, like I said before, make sure that you're returning inside your chained then statements:
senderMessageInbox(senderUID, user.uid, user.profileURL, user.username)
.then(() => {
console.log("recieverMessageInbox");
return recieverMessageInbox(user.uid, senderUID, currentUser.profileURL, currentUser.username)
})

Related

How do we retrieve a Boolean with exists in mongoose?

I am trying to check weather an account associated with the same username already exists or not. I am using the exist method to check but I keep getting a large object instead of a Boolean value.
async checkExisting(username,userCollection) { //WORK ON ISSUE WITH VERIFYING
const check = new Promise((resolve,reject) => {
let value = userCollection.exists({username})
console.log(value);
// if(userCollection.exists({username})) {
// reject("Username taken")
// }
resolve("Username avaliable")
})
return check;
},
Your code is correct. It's just what you write in resolve is returned.
And no need to make the function async as you're already returning a Promise. So where you call this function there just keep await as follows
await checkExisting('username', 'collection')
checkExisting(username, userCollection)
{
return new Promise((resolve, reject) => {
userCollection
.exists({username})
.then((value) => {
if (value) {
resolve(true)
}
resolve(false)
})
.catch((err) => reject(err))
})
}
Note: You can use either promise or async-await syntax; both ways are correct. However, never combine these two concepts as it will give unexpected output.
userCollection.exists({username}) returns a query that you never ran. You need to actually execute it and wait for the result. Also avoid the Promise constructor antipattern. Just do
async checkExisting(username,userCollection) {
const check = await userCollection.exists({username})
if (check) {
throw new Error("Username taken");
}
return "Username avaliable";
},

Firebase admin sdk calling the wrong catch

I am trying to add a new user with firebase-admin and then to save a new document in a custom collection.
Sample code following:
admin.auth().createUser(user)
.then((record) => {
user.uid = record.uid;
userCollection.doc(record.uid).set({...user})
.then(writeResult => {
resolve();
})
.catch(reason => {
reject(reason)
});
})
.catch((err) => {
reject(err);
});
The problem is, if the userCollection.doc(record.uid).set({...user}) fails, I expect the nested catch (with reason as param) to be called. Instead, always the outer one is called (with err as param).
Is there something wrong with the SDK or am I doing something wrong?
Thank you
This is because you don't return the promise returned by userCollection.doc(record.uid).set() and therefore you don't return the promises returned by the subsequent then() and catch() methods. In other words you don't return the promises chain.
But, actually, you should chain your Promises as follows and avoid a then()/catch() pyramid.
admin
.auth().createUser(user)
.then((record) => {
user.uid = record.uid;
return userCollection
.doc(record.uid)
.set({ ...user })
})
.catch((err) => {
// Here you catch the potential errors of
// the createUser() AND set() methods
console.log(JSON.stringify(err));
});
More details here, here and here.

How do i combine asynchronous js with vuex store?

So i've been trying to make a 'log in' for my vue app. User logs in by clicking on the button and running the following method:
async signIn() {
this.getfireBaseData(await this.authenticate());
},
Here are both of the methods used inside the previous one:
async authenticate() {
auth
.signInWithPopup(provider)
.then((result) =>
this.$store.commit("setUserData", {
email: result.additionalUserInfo.profile.email,
picture: result.additionalUserInfo.profile.picture,
name: result.additionalUserInfo.profile.name,
})
)
.catch((err) => console.log(err.message));
return this.$store.state.userData;
},
async getfireBaseData(x) {
db.collection("rooms")
.get()
.then((snapshot) => {
this.firebaseData = snapshot.docs.map((doc) => ({
id: doc.id,
data: doc.data(),
}));
console.log(x);
if (x) {
this.$router.push(`/rooms/${this.firebaseData[0].id}`);
}
});
},
And the store:
state: {
userData: null,
},
mutations: {
setUserData(state, payload) {
state.userData = payload;
},
}
I'm expecting it to run getfireBaseData as soon as the authenticate() resolves its promise. For some reason - when i'm running the code for the first time - authenticate() leaves me with fullfiled promise with the value of "null" - the value in the store. It seems like authenticate() does modify the store value but does not return it as i run it instantly. Any solves to that issue?
That's because you are returning directly your store data from authenticate(), not the promise chain.
So you are not waiting your auth.signInWithPopup but resolve directly the current value of your store.
try to returning directly auth.signInWithPopup(provider) and return your userData from the resolve callback or via an other "then" after your catch.
But it might not be a good idea to mix async/await with .then.catch.
It's error prone.

Am i promise chaining correctly?

onSubmit(e) {
e.preventDefault();
const user = {
fname: this.state.firstname,
lname: this.state.lastname,
email: this.state.email,
username: this.state.username,
password: this.state.password
}
new Promise((resolve,reject) => {
this.props.fetchUser(this.state.username)
.then(res => {
this.setState({failed: this.props.exists})
if(!this.state.failed)
this.props.registerUser(user)
})
.then(res => {
this.setState({registered: this.props.status});
resolve();
})
})
}
This is my attempt at chaining promises. The idea was that registered should update correctly to the state of this.props.status (true/false).
When this.props.registerUser is called in the first promise, it changes this.props.status to true. However, registered is being set to false (which is the value of this.props.status before registerUser is called), rather than true.
I know for sure that this.props.status is changing to true, but the state of registered isn't changing.
I'm new to this stuff.
I'm assuming that fetchUser and registerUser are functions that return promises. In that case, you do not need to wrap the call for fetchUser in a new Promise(...) since it will return a promise when invoked.
The reason that the second then(...) is not being called, is that you never return a promise from the first then(...).
if(!this.state.failed)
this.props.registerUser(user)
should become
if(!this.state.failed)
return this.props.registerUser(user)
With these two modifications, your code should look like so
this.props.fetchUser(this.state.username)
.then(res => {
this.setState({
failed: this.props.exists
});
if (!this.state.failed) {
return this.props.registerUser(user)
}
})
.then(res => {
this.setState({
registered: this.props.status
});
})
Furthermore, you would expect to read the result of fetchUser(...) on the res object rather than the component props.
One final caveat that you should be aware of is that setting the state and reading it immediately after, is not guaranteed to always work as expected. The safe way to do it, is to pass a function as your second argument to setState and that will be invoked when the state is updated by React.
The simplest way to do it in this case is to avoid reading the state altogether and instead using a temporary variable.
const exists = this.props.exists;
this.setState({
failed: exists
});
if (!exists ) {
return this.props.registerUser(user)
}

Using promises in React to wait for functions to finish within JavaScript map function

I've been trying to figure out what the proper way would be to write a promise for this function. I have an asynchronous function that makes an HTTP request to the server to retrieve a response, "documents_fileUploader." I am mapping through the "url" of each item within the response, and each url will go in to a function that makes another HTTP request and then sets the state. I want to fire the "upload()" function only after everything within the "documents_fileUploader()" function is complete. I tried doing this without a promise and it went straight to my "upload()" function because request was still pending. Any suggestions on how to go about this?
documents_fileUploader(formData).then(resp => {
resp.data.items.map(url => {
const key = url.split("/")[4];
this.setState({
urls: [...this.state.urls, url],
keys: [...this.state.keys, key]
});
this.getFileObject(key);
})
}).then(() => {
this.upload();
})
getFileObject = file => {
file_view(file).then(resp => {
this.setState({
mimeTypes: [...this.state.mimeTypes, resp.data.item.headers.contentType]
})
}).catch(err => {
console.log(err);
})
}
To your main question, you can wait for every promise that your .map call returns by using the Promise.all method.
Second, in order for that to work, your getFileObject function must return the promise it creates.
So incorporating those two changes, your snippet might look like:
documents_fileUploader(formData).then(resp => {
return Promise.all(resp.data.items.map(url => { // Wrap in Promise.all and return it
const key = url.split("/")[4];
this.setState({
urls: [...this.state.urls, url],
keys: [...this.state.keys, key]
});
return this.getFileObject(key); // Make sure to return this promise as well.
}));
}).then(() => {
// Now this won't happen until every `getFileObject` promise has resolved...
this.upload();
})
getFileObject = file => {
return file_view(file).then(resp => { // And return the promise here.
this.setState({
mimeTypes: [...this.state.mimeTypes, resp.data.item.headers.contentType]
})
}).catch(err => {
console.log(err);
})
}

Categories