Trouble understanding Cloud Function Error - javascript

I am new to cloud funcations node.js and type script. I am running the below code and getting the error below and can't make sense of it after watch a ton of videos about promises and searching other questions.
any help would be appreciated.
Function returned undefined, expected Promise or value
exports.compReqUpdated = functions.firestore
.document('/compRequests/{id}')
.onUpdate((change, contex)=>{
const newData = change.after.data();
//const oldData = change.before.data();
const dbConst = admin.firestore();
const reqStatus:string = newData.requestStatus;
const compId:string = newData.compID;
const reqActive:boolean = newData.requestActive;
if (reqStatus == "CANCELED" && reqActive){
const query = dbConst.collection('compRequests').where('compID', '==', compId);
const batch = dbConst.batch();
query.get().then(querySnapshot => {
const docs = querySnapshot.docs;
for (const doc of docs) {
console.log(`Document found at path: ${doc.ref.path}`);
console.log(doc.id);
const docRef = dbConst.collection('compID').doc(doc.id);
batch.update(docRef, {requestStatus: 'CANCELED',requestActive: false});
};
return batch.commit()
})
.catch(result => {console.log(result)});
}else{
return
}
});

The firebase docs state that the callback passed to the onUpdate function should return PromiseLike or any value, but you aren't returning anything right now. If you change your code to something as follows I reckon it should work as expected:
exports.compReqUpdated = functions.firestore
.document('/compRequests/{id}')
.onUpdate((change, contex) => {
const newData = change.after.data();
//const oldData = change.before.data();
const dbConst = admin.firestore();
const reqStatus: string = newData.requestStatus;
const compId: string = newData.compID;
const reqActive: boolean = newData.requestActive;
if (reqStatus == "CANCELED" && reqActive) {
const query = dbConst.collection('compRequests').where('compID', '==', compId);
const batch = dbConst.batch();
return query.get().then(querySnapshot => {
const docs = querySnapshot.docs;
for (const doc of docs) {
console.log(`Document found at path: ${doc.ref.path}`);
console.log(doc.id);
const docRef = dbConst.collection('compID').doc(doc.id);
batch.update(docRef, { requestStatus: 'CANCELED', requestActive: false });
};
return batch.commit()
}).catch(result => { console.log(result) });
} else {
return false;
}
});

Related

Firebase Cloud Functions Async

I am making a function for firebase cloud functions, I want a function to be called every time a new document is created in "posts". I want this function to perform the tasks that I put inside the "onCeatePost" function.
The problem I have is that I'm not sure if this is the correct way to structure such a function.
In several firebase examples I have seen that it is always called return _; or return null; at the end of a task, but I don't know how to structure the function so that all the tasks are carried out, could someone help me to restructure my function or tell me what is wrong please.
There are several if statements in the function, if the created publication does not comply with them, I would like it to skip them but continue with the other tasks that I put inside the function.
I don't know if it's too much to ask, but I'm new to this language and I haven't been able to find the answer I'm looking for. Thank you!
exports.onPostCreate = functions.firestore.document("/posts/{postId}").onCreate(async (snap) => {
const post = snap.data();
if (post) {
try {
const topic = post.topic;
const contentForFeed = post.contentForFeed;
const uid = post.uid;
const previous = post.prev;
await db.collection("users").doc(uid).update({"stats.posts": admin.firestore.FieldValue.increment(1)});
if (topic) {
await db.collection("topics").doc(topic.id).collection("user-authors").doc(uid).set({"date": snap.createTime});
}
if (contentForFeed == true) {
const userPath = db.collection("users").doc(uid);
await userPath.update({"stats.lastUpdate": snap.createTime});
}
if (previous) {
const previousId = previous.id;
const previousUid = previous.uid;
const refPrev = db.collection("posts").doc(previousId);
await db.runTransaction(async (t) => {
const doc = await t.get(refPrev);
const priority = doc.data().stats.date;
const newDate = new admin.firestore.Timestamp(priority.seconds + 120, priority.nanoseconds);
await db.collection("posts").doc(previousId).update({"newDate": newDate});
});
if (previousUid != uid) {
const path = db.collection("users").doc(uid).collection("user-posts");
const dataToSet = {"timestamp": snap.createTime, "uid": uid, "postId": onReplyToPostId};
await path(dataToSet);
}
}
} catch (err) {
functions.logger.log(err);
}
} else {
return null;
}
});
You'll find below the adapted code (untested) with 4 corrections.
Here are explanations for the two most important ones:
(Correction 2) In a transaction you need to use the transaction's update() method and not the "standard one"
(Correction 4) When all the asynchronous work is complete you need to return a value or a Promise. See this documntation page for more details.
exports.onPostCreate = functions.firestore
.document('/posts/{postId}')
.onCreate(async (snap) => {
const post = snap.data();
if (post) {
try {
const topic = post.topic;
const contentForFeed = post.contentForFeed;
const uid = post.uid;
const previous = post.prev;
await db
.collection('users')
.doc(uid)
.update({
'stats.posts': admin.firestore.FieldValue.increment(1),
});
if (topic) {
await db
.collection('topics')
.doc(topic.id)
.collection('user-authors')
.doc(uid)
.set({ date: snap.createTime });
}
if (contentForFeed == true) {
const userPath = db.collection('users').doc(uid);
await userPath.update({ 'stats.lastUpdate': snap.createTime });
}
let previousUid; // <= Correction 1
if (previous) {
const previousId = previous.id;
previousUid = previous.uid; // <= Correction 1
const refPrev = db.collection('posts').doc(previousId);
await db.runTransaction(async (t) => {
const doc = await t.get(refPrev);
const priority = doc.data().stats.date;
const newDate = new admin.firestore.Timestamp(
priority.seconds + 120,
priority.nanoseconds
);
t.update(refPrev, { newDate: newDate }); // <= Correction 2
});
if (previousUid != uid) {
const path = db
.collection('users')
.doc(uid)
.collection('user-posts');
const dataToSet = {
timestamp: snap.createTime,
uid: uid,
postId: onReplyToPostId,
};
await path.add(dataToSet); // <= Correction 3
}
}
return null; // <= Correction 4
} catch (err) {
functions.logger.log(err);
}
} else {
return null;
}
});

Firebase 9 array from onSnapshot

So I need to convert data from Firebase 9 snapshot to an array.
I've came up with something like this, but it doesn't return anything.
Check comments in my code.
I suppose, I should use promises, but can't figure out the right way.
const fetchNewMessages = async (threadId: string, latestTimestamp: any) => {
const q = query(threadRef(threadId), orderBy('timestamp', 'asc'), startAfter(latestTimestamp));
const messages: IMessage[] = [];
onSnapshot(q, (querySnapshot) => {
const docs = querySnapshot.docChanges();
docs.forEach((change) => {
if (change.type === "added")
messages.push(fetchDoc(change.doc));
});
console.log(messages.length); // here I get 34
});
console.log(messages.length); // here I get 0
return messages.reverse();
};
try to wait for "onSnapshot"
const fetchNewMessages = (threadId: string, latestTimestamp: any) => {
const q = query(threadRef(threadId), orderBy('timestamp', 'asc'), startAfter(latestTimestamp));
const messages: IMessage[] = [];
return new Promise((resolve) => {
onSnapshot(q, (querySnapshot) => {
const docs = querySnapshot.docChanges();
docs.forEach((change) => {
if (change.type === "added")
messages.push(fetchDoc(change.doc));
});
resolve(messages.reverse());
});
});
};

Add documentID of fetched document to array in firebase cloud function

I have a cloud function that "Joins" data from a list of documents in a collection.
I then return the result as an array, but I want to return the documentId as well (doc.id) in the list that i return.
How can i do that?
const restData = [];
//const userId = ctx.auth.uid;
const userId = 'dHAP1CNN6LhJWddQoTqyIkqIjhB2'; // !!! TEST ONLY
const all = await db.collection(`/customers/${userId}/lunch_cards`).listDocuments().then((snapshot) => {
snapshot.forEach(doc => {
const nextData = db.collection(`/restaurants`).doc(doc.id).get();
const newData = {...nextData, documentId: doc.id}; <-- This does not work only documentId isout in newData
console.log(util.inspect(newData));
restData.push(nextData);
console.log(doc.id);
});
});
const snaps = await Promise.all(restData);
const responseArray = snaps.map((s) => {return s.data()});
return responseArray;
I solved it!
Solution:
Just adding a new string to the array :)
const responseArray = snaps.map((s) => {
const snapData = s.data();
if (snapData) {
snapData['id'] = s.id;
}
return snapData;
});

On Deploying the Firebase Cloud Function getting an error of "Each then should return a value or throw"

I am new to Cloud Functions so I having issues with below code, the error is in the last part where the console.log is mentioned, please assist what shall I been done to deploy the Function successfully, as I am following a tutorial there is no such error for the name.
'use-strict'
const functions = require('firebase-functions');
const admin = require("firebase-admin");
admin.initializeApp(functions.config().firebase);
exports.sendNotifications = functions.firestore.document("users/{user_id}/notifications/{notification_id}").onWrite(event => {
const user_id = event.params.user_id;
const notification_id = event.params.notification_id;
return admin.firestore().collection("users").doc(user_id).collection("notifications").doc(notification_id).get().then(queryResult => {
const from_user_id = queryResult.data().from;
const message = queryResult.data().message;
const from_data = admin.firestore().collection("users").doc(from_user_id).get();
const to_data = admin.firestore().collection("user").doc(user_id).get();
return Promise.all([from_data, to_data]).then(result => {
const from_name = result[0].data().name;
const to_name = result[1].data().name;
const token_id = result[1].data().token_id;
const payload = {
notification: {
title: "Notification from:" + from_name,
body: message,
icon: "default"
}
};
return admin.messaging().sendToDevice(token_id, payload).then(result =>{
console.log("notifcation sent");
});
});
});
});
By chaining your Promises and returning null in the last then(), as follows, you should solve your problem:
exports.sendNotifications = functions.firestore.document("users/{user_id}/notifications/{notification_id}").onWrite(event => {
const user_id = event.params.user_id;
const notification_id = event.params.notification_id;
return admin.firestore().collection("users").doc(user_id).collection("notifications").doc(notification_id).get()
.then(queryResult => {
const from_user_id = queryResult.data().from;
const message = queryResult.data().message;
const from_data = admin.firestore().collection("users").doc(from_user_id).get();
const to_data = admin.firestore().collection("user").doc(user_id).get();
return Promise.all([from_data, to_data]);
})
.then(result => {
const from_name = result[0].data().name;
const to_name = result[1].data().name;
const token_id = result[1].data().token_id;
const payload = {
notification: {
title: "Notification from:" + from_name,
body: message,
icon: "default"
}
};
return admin.messaging().sendToDevice(token_id, payload)
})
.then(messagingResponse => {
console.log("notification sent");
return null; //Note the return null here, watch the 3 videos about "JavaScript Promises" from the Firebase video series: https://firebase.google.com/docs/functions/video-series/
});
});
You may have a look at the corresponding MDN documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#Chaining
Also, note that, in your code, it seems that you are not using the to_name constant.

How to use the beforeEach in node-tap?

Can someone provide an example on how to use the beforeEach? http://www.node-tap.org/api/
Ideally, an example of the promise version, but a callback version example would also be nice.
Here is a test I created which works fine:
'use strict';
const t = require('tap');
const tp = require('tapromise');
const app = require('../../../server/server');
const Team = app.models.Team;
t.test('crupdate', t => {
t = tp(t);
const existingId = '123';
const existingData = {externalId: existingId, botId: 'b123'};
const existingTeam = Team.create(existingData);
return existingTeam.then(() => {
stubCreate();
const newId = 'not 123'
const newData = {externalId: newId, whatever: 'value'};
const newResult = Team.crupdate({externalId: newId}, newData);
const existingResult = Team.crupdate({externalId: existingId}, existingData);
return Promise.all([
t.equal(newResult, newData, 'Creates new Team when the external ID is different'),
t.match(existingResult, existingTeam, 'Finds existing Team when the external ID exists')
]);
});
})
.then(() => {
process.exit();
})
.catch(t.threw);
function stubCreate() {
Team.create = data => Promise.resolve(data);
}
Before I do anything, I want to persist existingTeam. After it's saved, I want to stub Team.create. After these two things, I want to start actually testing. I think it would be cleaner if instead of using a Promise.all or perhaps duplicating the test code, I could use beforeEach.
How would I convert this to use beforeEach? Or what is an example of its usage?
Simple, just return promise from callback function
const t = require('tap');
const tp = require('tapromise');
const app = require('../../../server/server');
const Team = app.models.Team;
const existingId = '123';
const existingData = {
externalId: existingId,
botId: 'b123'
};
t.beforeEach(() => {
return Team.create(existingData).then(() => stubCreate());
});
t.test('crupdate', t => {
t = tp(t);
const newId = 'not 123'
const newData = {
externalId: newId,
whatever: 'value'
};
const newResult = Team.crupdate({
externalId: newId
}, newData);
const existingResult = Team.crupdate({
externalId: existingId
}, existingData);
return Promise.all([
t.equal(newResult, newData, 'Creates new Team when the external ID is different'),
t.match(existingResult, existingTeam, 'Finds existing Team when the external ID exists')
]);
}).then(() => {
process.exit();
}).catch(t.threw);
function stubCreate() {
Team.create = data => Promise.resolve(data);
}

Categories

Resources