Chaining an action behind an asynchronous function - javascript

I'm currently trying to create a chain of actions that only happen sequentially, and not asynchronously.
It's a sign-in function, where I want the following actions to happen in this order:
User clicks on sign in
Sign in request sent
If successful, data fetch is executed
Console log that it's successful
Then push to dashboard with fetched data
However what's actually happening is the following:
User clicks on sign in
Sign in request sent
If successful, data fetch is started
User pushed to dashboard
Data fetch continues, and then re-renders with the data
I tried integrating async / await, but weirdly it's not doing it as I expected. I think it's me misunderstanding what I need to do more than anything.
Here's the function I want all of this to happen through:
const handleSignInSubmit = async (e) => {
e.preventDefault()
await signIn(email, password)
console.log("Trying to push")
history.push("/dashboard")
}
And here is my sign-in function:
const signIn = (email, password) => {
firebase.auth().signInWithEmailAndPassword(email, password)
.then((userCredential) => {
var user = userCredential.user;
setCurrentUser(user)
})
.then(async () => {
console.log("Fetching data...")
await fetchData()
})
.catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
console.log(errorCode)
console.log(errorMessage)
});
}
Can anyone tell me what I'm doing wrong here?

You are awaiting the return value of signIn.
signIn doesn't have a return statement so it returns undefined.
You can only usefully await a promise.
return the return value of firebase.auth.etc.etc.catch() which is the promise you have been working with.

Related

SignIn Authentication with Firebase issue

The try and catch method is not verifying if the email and password the user entering exists in the firebase, but it shows an error message on the console.
function login(email, password) {
return auth.signInWithEmailAndPassword(email, password);
}
const handleLogin = (e) => {
e.preventDefault();
try {
setError("")
login(emailRef.current.value, passwordRef.current.value);
} catch {
setError("The email or password you entered is incorrect");
}
}
The call to signInWithEmailAndPassword(email, password) is an asynchronous call and returns a promise.
A promise is essentially an asynchronous task that will either complete or fail. Read more about promises here Promises.
In this scenario, the function must contact the Firebase auth servers, attempt to fetch the user account, and then return either an error or sign the user into their account.
Why isn't it showing the error? Well, your function is finishing its execution before it has received a response from the server. If there is no response, then it doesn't know that there is an error. In order to fix this,
you must wait for the promise to resolve. To do so, I recommend using the async/await syntax.
Modify your function to be like so:
const handleLogin = async (e) => {
e.preventDefault();
try {
setError("")
await login(emailRef.current.value, passwordRef.current.value);
}catch {
setError("The email or password you entered is incorrect");
}
}

Getting current user after sign in with Firebase

I want to send a verification email after user sign up. I wrote that code but if i want to get the current user with firebase on react native it always return null. How can i fix it?
Here is the sign up function:
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then(navigation.navigate("WaitingRoom"))
.catch((err) => console.log(err));
And also email verification function in WaitingRoom:
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
user
.sendEmailVerification()
.then(() => {
console.log(":)");
})
.catch((err) => {
console.log(err);
});
} else {
console.log(":(");
}
});
I also tried firebase.auth().currentUser but it return null too.
From your comment above: "It happens but there is a delay and sometimes i have to reload page. I think i have to wait for response or something like that."
Yes, as explained in the doc, you need to wait that the Auth object has finished initializing.
This is why you should:
Either use the onAuthStateChanged() observer and put the desired code/ business logic in the if (user) {} block, where you are sure user is not null.
OR, manually check that firebase.auth().currentUser is not null before triggering the code/ business logic.
Concretely, based on what I understand from your question, you could/should call navigation.navigate("WaitingRoom") in the onAuthStateChanged() observer, as follows:
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
user
.sendEmailVerification()
.then(() => {
navigation.navigate("WaitingRoom");
})
.catch((err) => {
console.log(err);
});
} else {
console.log(":(");
}
});

Authenticate function not waiting for promise to resolve

I'm making an authenticate function. It actually works with hardcoded users, but when I start getting users from Firebase, things start getting asynchronous and issues with timing start happening.
I've got a kind of long-winded Javascript function here that I believe returns a promise.
function authenticate({ username, password }) {
return users.then((querySnapshot) => {
return querySnapshot.forEach(doc => {
let user = doc.data();
if (user.username.toUpperCase() == username.toUpperCase())
return bcrypt.compare(password, user.password).then(function (result) {
console.log(password);
console.log(user.password);
console.log(result);
if (result) {
const token = jwt.sign({ sub: user.id }, config.secret);
const { password, ...userWithoutPassword } = user;
return {
...userWithoutPassword,
token
};
}
})
})
})
}
Console logging seems to confirm that this is a promise. I'll be honest, I copy-pasted a lot of the code inside, so I'm still not entirely sure how it works, but the promise syntax is at least something I'm confident in. After I go through a list of users pulled from Firebase and check that both username and password match, the guts of if (result) should run. result does come back as true, which is correct for what I'm trying to log in with, but my password form rejects me because it continues processing before the authenticate method is finished.
In another Javascript file, I have the method that calls this one.
function authenticate(req, res, next) {
console.log(req.body);
userService.authenticate(req.body)
.then(user => console.log(user))
//.then(user => user ? res.json(user) : res.status(400).json({ message: 'Username or password is incorrect' }))
.catch(err => next(err));
}
I'm learning a lot about asynchronous programming recently but this is defying my expectations a bit. Surely doing .then() on authenticate() should run authenticate(), get a promise, even if it's unresolved, then wait for it to resolve before executing the rest of the statements? The current issue is that the method goes ahead, finds no value for user, then throws a 400, which I think is an issue with asynchronicity. Can anyone explain why the outer authenticate function isn't waiting and how I could make it do that?
There are two possible issues:
Result of forEach
The forEach function returns undefined, see Array.prototype.forEach(). If you need the result of the iteration, you can use Array.prototype.map()
Waiting for the Promise
The following statement sounds like the code does not await the result properly:
my password form rejects me because it continues processing before the authenticate method is finished.
If you have a javascript promise, you can use the await keyword in order to continue the function execution only if the promise is either resolved or rejected. Have a look at the examples here: https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/await
In your example, the authenticate function would look like this:
async function authenticate(req, res, next) {
console.log(req.body);
await userService.authenticate(req.body)
.then(...)
.catch(...);
}
Note the async and await keywords. That way it only returns after the userService.authenticate(..) call is fully processed.
Firebase QuerySnapshot has a docs property of type Array<QueryDocumentSnapshot<T>>. You can use that and the Array.find to search for the user. You should also await for bcrypt.compare while you search for user.
function authenticate({ username, password }) {
return users.then(async (querySnapshot) => {
const { usersDocs: docs } = querySnapshot;
const userDoc = usersDocs.find(doc => {
return doc.data().username === username;
});
if (userDoc) {
let user = doc.data();
const pwdCompareResult = await bcrypt.compare(password, user.password);
console.log(password);
console.log(user.password);
console.log(pwdCompareResult );
if (pwdCompareResult ) {
const token = jwt.sign({ sub: user.id }, config.secret);
const { password, ...userWithoutPassword } = user;
return {
...userWithoutPassword,
token
}
}
}
})
}
Please consider using Firebase Authentication instead
Your auth implementation is not reliable and it transfers sensitive data to every users device, like usersname and password hashes. Firebase has a very solid authentication system that you should be using.

Firestore code is not waiting for response

I'm new to React Native, Javascript and Firestore. The issue is that I'm trying to read a firestore database and wait for the response to display information on the next page before it is rendered.
Here is the controller code:
_signIn = async () => {
try {
// Google sign in and connect to Firebase
... This code is working as expected
// Connect to the Firebase Firestore and store the connection
CesarFirestore.setFirestore(firebase.firestore());
let cFS = CesarFirestore.getInstance();
cFS.saveLoginDetails(this.cUser);
// THIS IS THE CALL TO GET THE STATUS OF THE USER
await cFS.readAvailability(this.cUser);
this.setState({loggedIn: true});
} else {
... INVALID login coding works fine
}
} catch (error) {
... CATCH coding works fine
}
}
};
From the above marked line, the following code is executed:
async readAvailability(cUser) {
let tuesdayDate = new CesarDate().getTuesday();
let availableRef = CesarFirestore.getFirestore().collection('available');
let availableQuery = await availableRef
.where('tuesdayDate', '==', tuesdayDate)
.where('userId', '==', cUser.getUserId())
.get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
cUser.setTuesdayAvailability('NOT_FOUND');
}
snapshot.forEach(doc => {
cUser.setTuesdayAvailability(doc.get('status'));
});
})
.catch(err => {
console.log('Error getting documents', err);
});
}
So the readAvailability(cUser) code should wait for the availableQuery to return results. The results would be then stored in the cUser class which is available to the rest of the application.
What happens is sometimes the result is available and sometimes the result is null. I thinking this is because doc is undefined which I have confirmed via the debugger.
Any help would be great and thank you in advance
If you await for a function, it must return a value, or a promise that resolves to a value later on. Since you're instead consuming the promise (in your then() handler), there is no way for the interpreter to know what to wait on.
async readAvailability(cUser) {
let tuesdayDate = new CesarDate().getTuesday();
let availableRef = CesarFirestore.getFirestore().collection('available');
let snapshot = await availableRef
.where('tuesdayDate', '==', tuesdayDate)
.where('userId', '==', cUser.getUserId())
.get()
if (snapshot.empty) {
console.log('No matching documents.');
cUser.setTuesdayAvailability('NOT_FOUND');
}
snapshot.forEach(doc => {
cUser.setTuesdayAvailability(doc.get('status'));
});
return true;
}
I'd usually actually make readAvailability return the user it modified, but the above should work too.

Listening to update within a function

I've got a https onRequest cloud function that writes a doc. That doc triggers another cloud function, which adds a field to the doc based on a calculation.
Any suggestions how to make the res of the https onRequest function to wait until that field is added and to respond to the https with the value of that field?
exports = module.exports = functions.https.onRequest(async (req, res) => {
const user = await admin.firestore().collection("users").doc();
const enquiryId = await admin.firestore().collection("users").doc(user.id).collection("enquiries").doc();
await enquiryId.set({
value: req.query.value;
});
let getQ = functions.firestore.document('users/{user.id}/enquiries/{enquiryId.id}').onUpdate((change, context) => {
const newValue = change.after.data();
q = newValue.quote;
return q
});
getQ;
return res.status(200).send(getQ);
});
Update following your comments below:
Since the "cloud function gets triggered when we write to the firestore from mobile or web" you could set up a listener to the users/{user.id}/enquiries/{enquiryId.id} document.
You just have to send back the values of user.id and enquiryId.id as response to the HTTP Cloud Function, as follows:
await enquiryDocRef.set(data);
return res.status(200).send({userDocID : user.id, enquiryDocId: enquiryId.id});
and you use these two values to set the listener. The doc is here: https://firebase.google.com/docs/firestore/query-data/listen
This way, when reaching step 4 described in your comment, i.e. when "based on the calculation, a field in the (users/{user.id}/enquiries/{enquiryId.id}) doc gets updated", your listener will be triggered and you will get this updated field value in your front-end.
Initial answer:
If you want the HTTP Cloud Function to "wait until that field is added and to respond to the https with the value of that field", you just have to wait that the asynchronous set() operation is completed before sending back the response.
You can detect that the operation is completed when the Promise returned by the set() method resolves.
Since you do await enquiryId.set({...}) you are therefore very close to the solution: await will make the Cloud Function wait until that Promise settles (is not pending anymore), so you just have to return the response right after this line, as follows:
Therefore just do as follows:
exports = module.exports = functions.https.onRequest(async (req, res) => {
try {
const userDocRef = await admin.firestore().collection("users").doc();
const enquiryDocRef = await admin.firestore().collection("users").doc(userDocRef.id).collection("enquiries").doc();
const data = {
value: req.query.value
};
await enquiryDocRef.set(data);
return res.status(200).send(data);
} catch (error) {
return res.status(400).send(error);
}
});

Categories