I'm using firebase admin SDK to create new User to my web app.
// function to create user and store additional info in firestore
exports.createUser = functions
.https.onCall((data, context) => {
admin.auth().createUser({
phoneNumber: data.phoneNumber,
displayName: data.displayName,
}).then((user) => {
// store data in firestore
admin.firestore().collection("user").doc(user.uid).set({...data});
}).catch((err) => {
// handle error
});
});
When I call the function from the client (attach it to onClick event) I want to wait till user is successfully added in firestore and invoke the fetchUser function so I can see the new list of data with the newly added user. Currently fetchUser gets called and page refreshes but I cannot see the newly added user before refreshing it.
const createUser = functions.httpsCallable('createUser');
const createNewUser = () => {
createUser({
phoneNumber: "+1111111111111",
displayName: "test",
introduction: "testcreate",
})
.then((res) => {
fetchUser(); // fetchUser just fetches user data from firestore and rerenders page
})
.catch((err) => {
console.log(err);
});
};
To summarize, can I know when cloud function will complete its job and run a particular function or task ?
As you will see in the three videos about "JavaScript Promises" from the official Firebase video series you MUST return a Promise or a value in a background triggered, Pub/Sub or Callable Cloud Function when all the asynchronous operations are complete.
This has two linked effects:
When the asynchronous operations are complete, it indicates to the Cloud Function platform that it can terminate and clean up your function.
On the opposite, until the asynchronous operations are complete, it indicates to the Cloud Function platform that it should wait before terminating the Cloud Function.
To return a Promise, since you chain several asynchronous Firebase Admin SDK methods (which return Promises), you need to return the Promise chain as follows:
exports.createUser = functions.https.onCall((data, context) => {
return admin // <== See return here
.auth()
.createUser({
phoneNumber: data.phoneNumber,
displayName: data.displayName,
})
.then((user) => {
// store data in firestore
return admin // <== See return here
.firestore()
.collection('user')
.doc(user.uid)
.set({ ...data });
})
.then(() => {
// The set() method returns Promise<void>
// To send data back to the client, return data that can be JSON encoded
return { result: 'user created' };
})
.catch(error => {
// See https://firebase.google.com/docs/functions/callable#handle_errors
// on how to handle errors
});
});
This way, when your front-end gets back the Callable Cloud Function response, you are sure that the user and the Firestore document were created.
If you want to use the async/await keywords, do as follows. Note that it is not recommended to mixup then() with async/await.
exports.createUser = functions
.https.onCall(async (data, context) => { // <== See async here
try {
const user = await admin.auth().createUser({
phoneNumber: data.phoneNumber,
displayName: data.displayName,
});
await admin.firestore().collection("user").doc(user.uid).set({ ...data });
return { result: 'user created' }
} catch (error) {
console.log(error); // If desired
// See https://firebase.google.com/docs/functions/callable#handle_errors
// on how to handle errors
}
});
See how the code is much more readable, as if it was synchronous code.
It looks like you are not returning the promises correctly and hence the function terminates before the docs are updated. Please try adding the return statements as shown below.
exports.createUser = functions
.https.onCall((data, context) => {
return admin.auth().createUser({
phoneNumber: data.phoneNumber,
displayName: data.displayName,
}).then(async (user) => {
// store data in firestore
await admin.firestore().collection("user").doc(user.uid).set({...data});
return {data: user.uid}
}).catch((err) => {
// handle error
return {error: error}
});
});
Then you should ideally received the response after all promises are resolved (i.e. the documents are updated). Also make sure you don't have Firestore local caching enabled.
Related
Building a NodeJS REST API.
Trying to send load data from FireBase collection, then sending it to the user (as API response).
Looks like the problem is that it's not waits for the firebase fetch to resolve, but send back a response without the collection data. (tried to use ASYNC-AWAIT but its not working)
exports.getChatMessages = async (req, res, next) => {
const chatId = req.params.chatId
const getChatData = () => {
db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot((snapshot) => {
snapshot.docs.forEach(msg => {
console.log(msg.data().messageContent)
return {
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
}
})
})
}
try {
const chatData = await getChatData()
console.log(chatData)
res.status(200).json({
message: 'Chat Has Found',
chatData: chatData
})
} catch (err) {
if (!err.statusCode) {
err.statusCode(500)
}
next(err)
}
}
As you can see, I've used 2 console.logs to realize what the problem, Terminal logs looks like:
[] (from console.logs(chatData))
All messages (from console.log(msg.data().messageContent))
Is there any way to block the code unti the firebase data realy fetched?
If I correctly understand, you want to send back an array of all the documents present in the messages subcollection. The following should do the trick.
exports.getChatMessages = async (req, res, next) => {
const chatId = req.params.chatId;
const collectionRef = db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc');
try {
const chatsQuerySnapshot = await collectionRef.get();
const chatData = [];
chatsQuerySnapshot.forEach((msg) => {
console.log(msg.data().messageContent);
chatData.push({
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
});
});
console.log(chatData);
res.status(200).json({
message: 'Chat Has Found',
chatData: chatData,
});
} catch (err) {
if (!err.statusCode) {
err.statusCode(500);
}
next(err);
}
};
The asynchronous get() method returns a QuerySnapshot on which you can call forEach() for enumerating all of the documents in the QuerySnapshot.
You can only await a Promise. Currently, getChatData() does not return a Promise, so awaiting it is pointless. You are trying to await a fixed value, so it resolves immediately and jumps to the next line. console.log(chatData) happens. Then, later, your (snapshot) => callback happens, but too late.
const getChatData = () => new Promise(resolve => { // Return a Promise, so it can be awaited
db.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot(resolve) // Equivalent to .onSnapshot((snapshot) => resolve(snapshot))
})
const snapshot = await getChatData();
console.log(snapshot)
// Put your transform logic out of the function that calls the DB. A function should only do one thing if possible : call or transform, not both.
const chatData = snapshot.map(msg => ({
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
}));
res.status(200).json({
message: 'Chat Has Found',
chatData
})
Right now, getChatData is this (short version):
const getChatData = () => {
db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot((snapshot) => {}) // some things inside
}
What that means is that the getChatData function calls some db query, and then returns void (nothing). I bet you'd want to return the db call (hopefully it's a Promise), so that your await does some work for you. Something along the lines of:
const getChatData = async () =>
db
.collection('chats')
// ...
Which is the same as const getChatData = async() => { return db... }
Update: Now that I've reviewed the docs once again, I see that you use onSnapshot, which is meant for updates and can fire multiple times. The first call actually makes a request, but then continues to listen on those updates. Since that seems like a regular request-response, and you want it to happen only once - use .get() docs instead of .onSnapshot(). Otherwise those listeners would stay there and cause troubles. .get() returns a Promise, so the sample fix that I've mentioned above would work perfectly and you don't need to change other pieces of the code.
I am trying to write a firebase cloud function that essentially does 2 things for now:
1) gets triggered whenever a user comes online/offline
2) fetch all the conversations for this user.
The below code is able to perform Step 1, but returns Function returned undefined, expected Promise or value Error in the console.
I am not sure how to write Promises and this is my first Firebase function. Please assist.
exports.offlineHandler = functions.database.ref('/users/{userid}/status')
.onUpdate((change, context) => {
const status = change.after.val();
const userId = context.params.userid;
console.log('the user is now ', status, "with mobile no ", userId);
// fetch a users conversations list
if (status === "offline") {
console.log("offline exec start");
return fetchUserConversations(userId)
.then(results => {
console.log("offline exec end");
for (result in results) {
console.log("id is", result);
}
return results;
}).catch(err => {
console.error("An error has occurred.", err);
return err;
});
} else {
console.log("user came online");
}
return null;
});
function fetchUserConversations(userId) {
return admin.firestore().collection('users/${userId}/conversations').get()
.then(snapshot => {
var conversations = [];
snapshot.forEach(doc => {
console.log("This is conversation id => ", doc.id);
conversations.concat(doc.id);
});
return conversations;
//return conversations;
}).catch(err => {
console.error(err);
return err;
});
}
Syntactically, you seem to be just be missing a return before fetchUserConversations(userId).... So:
return fetchUserConversations(userId).then(result=>{
To learn more about promises in Cloud Functions, you'd do best to study the Firebase documentation on sync, async, and promises, and Doug Stevenson's excellent video series on using promises in Cloud Functions.
A more fundamental problem with your code is that it doesn't do anything with the conversations it fetches for the user. It seems that you're trying to return them from the Cloud Function, but Cloud Functions that are triggered by a database write cannot return anything. Such Functions are triggered by a database write, not by a user operation.
While in your case it was a user operation (coming back online) that triggered the Cloud Function, that's an indirect trigger and you can't return anything to the user from the Cloud Function.
You have two main options here:
Write the conversations somewhere in the database that the user then looks them up.
Change the Cloud Functions trigger type to be HTTPS or Callable, and then call this Cloud Function from your application code when the user goes back online.
I'm trying to write a cloud function where if my app changed some string in user's firestore DB. A cloud function need to send a push notification. Database architecture is Messages => {UID} => UpdatedMessages . The problem is I cannot figure How to retrive which updateMessage under which UID has been updated.
const functions = require('firebase-functions');
const admin = require('firebase-admin')
admin.initializeApp()
const toUpperCase = (string)=> string.toUpperCase()
var registrationToken = 'dfJY6hYzJyE:APdfsdfsdddfdfGt9HMfTXmei4QFtO0u1ePVpNYaOqZ1rnDpB8xfSjx7-G6tFY-vWQY3vDPEwn_iZVK2PrsGUVB0q9L_QoRYpLJ3_6l1SVHd_0gQxJb_Kq-IBlavyJCkgkIZ';
exports.sendNotification = functions.firestore
.document('messages/{userId}/{updatedMessage}')
.onUpdate((change, context) => {
var message = {
data: {
title: 'Update',
body: 'New Update'
},
token: registrationToken
};
// Send a message to the device corresponding to the provided
// registration token.
admin.messaging().send(message)
.then((response) => {
// Response is a message ID string.
console.log('Successfully sent messagesssss:', response);
})
.catch((error) => {
console.log('Error sending message:', error);
});
});
Simply I need to rerive "var registrationToken" from UID .
You have to use the params property of the context object as follows
exports.sendNotification = functions.firestore
.document('messages/{userId}/{updatedMessage}')
.onUpdate((change, context) => {
const userId = context.params.userId;
const updatedMessage = context.params.updatedMessage;
var message = {
data: {
title: 'Update',
body: updatedMessage //For example, use the value of updatedMessage here
},
//...
};
//IMPORTANT: don't forget to return the promise returned by the asynchronous send() method
return admin.messaging().send(message)
.then((response) => {
// Response is a message ID string.
console.log('Successfully sent messagesssss:', response);
return null;
})
.catch((error) => {
console.log('Error sending message:', error);
return null;
});
});
See https://firebase.google.com/docs/functions/firestore-events#wildcards-parameters and https://firebase.google.com/docs/reference/functions/functions.EventContext#params for more info.
About the remark in the above code noted as "IMPORTANT", you may watch the official Firebase video series here: https://firebase.google.com/docs/functions/video-series/. In particular watch the three videos titled "Learn JavaScript Promises" (Parts 2 & 3 especially focus on background triggered Cloud Functions, but it really worth watching Part 1 before).
I'm creating an API using Express JS (Express 4) framework. I'm pretty new to NodeJS so I would like to know the best way to perform multiple async calls inside the same function.
For this example, I've got a function called Login in my Login Controller. Inside this function, let's say I gotta make quite a few async calls for authenticating a user, saving login info and similar functionalities like that.
But for some async calls, data to be processed should be fetched from the previous async call (they are dependent functions) and some functions are not like that.
Right now, this is how I'm doing the calls.
exports.login = function (req, res) {
var user = {
email: 'fakeemail#id.com',
password: 'password'
};
//Async call 1
Login.authUser(user, function (err1, rows1) {
var user_id = rows1[0].id;
//Async call 2 (depends on async call 1 for user_id)
Login.fetchUserDetails(user_id, function (err2, rows2) {
//Async call 3
Login.updateLoginInfo(param3, function (err3, rows3) {
//Some functionality occurs here then async call 4 happens
//Async call 4
Login.someOtherFunctionality(param4, function (err4, rows4) {
//return response to user
res.json({
success: true
});
});
});
});
});
};
Now all these async calls are nested. Is there any other way I can do this?
P.S: I didnt add error handling in this example
you can use promise as well. It will make you syntax more pretty. Your code will look like
Login.authUser(user).
then(fetchUser).
then(updateLoginInfo).
then(someOtherFunctionality).
catch(function(error){
//log your error or whatever
});
You can use Promise as suggested by Shahzeb.
Promises are objects that get resolved in future (asynchronous). Once a Promise is completed it either resolves or rejects. All promises resolved are then ed and those get rejected are catch ed
pseudo code
let myPromise = function() {
return new Promise(function(resolve, reject) {
resolve('foo');
});
};
myPromise().then( (v) => {
console.log(v);
})
Use Promise Chaining
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
alert(result); // 1
return result * 2;
}).then(function(result) { // (***)
alert(result); // 2
return result * 2;
}).then(function(result) {
alert(result); // 4
return result * 2;
});
More details refer Promise Chaining and chain promises in javascript
Using async/await (Requires Node.js v7.6)
This strategy also uses promises. In my opinion it improves readability since you are refactoring out each individual promise call into separate methods.
Codepen
// mock async calls
const authUser = user => Promise.resolve([{ id: 1 }]); // returns users id
const fetchUserDetails = user_id => Promise.resolve({ name: 'Fred', age: '10000' }); // returns users details
const updateLoginInfo = param3 => Promise.resolve({ status: 'success' }); // returns success?
const someOtherFunctionality = param3 => Promise.resolve({ field: 'value' }); // returns something
// all async functions return a promise
const login = async (/*req, res*/) => {
// User object
const user = {
email: 'fakeemail#id.com',
password: 'password'
};
// Async call 1
console.log(`Authorizing user...`);
const rows1 = await authUser(user);
const user_id = rows1[0].id;
console.log(`User ${user_id} authorized.`);
// Async call 2 (depends on async call 1 for user_id)
console.log(`Fetching user detail...`);
const rows2 = await fetchUserDetails(user_id);
console.log(`User Detail was fetched: ${JSON.stringify(rows2)}`);
// Async call 3
console.log(`Updating login info...`);
const param3 = `something`;
const rows3 = await updateLoginInfo(param3);
console.log(`Login info was successful: ${JSON.stringify(rows3)}`);
// Some functionality occurs here then async call 4 happens
console.log(`\nDoing stuff after async call 3, but before async call 4....\n`);
// Async call 4
console.log(`Async call 4...`);
const param4 = `something`;
const rows4 = await someOtherFunctionality(param4);
console.log(`END OF LOGIN FUNCTION`);
return 'returned value';
}
// run the async function
login()
.then(result => {
// respond
// res.json({ success: true });
console.log(`Promise value: ${result}`);
console.log(`Response: { success: true }`);
})
.catch(err => {
console.log(err);
})
I'm new to Express framework and learning, I'm having a problem using .then. The problem is I have 2 functions and I want the first one to complete before the second to start executing. I'm exporting the modules.
var ubm = require('./userBasic');
There are 2 functions setUserData and showUserId, the showUserId must execute only after setUserData has performed its operation.
var userId = ubm.setUserData(userName,userEmail,userDOB,moment);
userId.then(ubm.showUserId(userId));
Below are the 2 functions:
module.exports = {
setUserData: function (userName,userEmail,userDOB,moment){
//Performing database activities
return userId;
}
showUserId: function (userId){
console.log(userId);
}
}
When i run it says TypeError: Cannot read property 'then' of undefined.
Like I said I'm very new and learning and was unable to figure out the solution. I did some google search and got a brief about promise, but I don't know how to implement here.
Try using promises
module.exports = {
setUserData: function(userName, userEmail, userDOB, moment) {
return new Promise(function(resolve, reject) {
//db stuff
reject(error);
resolve(userId);
});
},
showUserId: function(userId) {
console.log(userId);
};
};
So in your execution you would write
ubm.setUserData(username, usereEmail, userDOB, moment)
.then((data) => {
showUserId(data);
})
.catch((err) => {
console.log(err);
});
A couple of things to note is that in this instance you could just log data without the need for another function like
ubm.setUserData(username, usereEmail, userDOB, moment)
.then((data) => {
console.log(data);
})
.catch((err) => {
console.log(err);
});
Whatever value you pass into resolve() will be returned as well as you pass errors into reject().