Firebase Cloud Functions / Each then() should return a value or throw promise/always-return - firebase

I was following the official firebase tutorial on promises (https://www.youtube.com/watch?v=7IkUgCLr5oA) but in my case, I cannot make it work.
const promise = userRef.push({text:text});
const promise2 = promise.then((snapshot) => {
res.status(200).json({message: 'ok!'});
});
promise2.catch(error => {
res.status(500).json({message: 'error'});
});
What am I doing wrong? Each then() should have its response in case something goes wrong, but that is why I am writing the promise2 catch.

Just add the return before sending the response.
const promise = userRef.push({text:text});
const promise2 = promise.then((snapshot) => {
return res.status(200).json({message: 'ok!'});
});
promise2.catch(error => {
return res.status(500).json({message: 'error'});
});
Also you can chain the promises as follows:
return userRef.push({text:text})
.then((snapshot) => {
return res.status(200).json({message: 'ok!'});
}).catch(error => {
return res.status(500).json({message: 'error'});
});

Related

Cloud firebase batch update

I am trying to use a batch update to update a count within each snapshot. But it seems like the function doesnt even run. I know it has something to do with the second promise but I am not sure where.
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
export const replyCreated = functions
.firestore
.document(`/Reply/{replyId}`)
.onCreate((change: any, context: functions.EventContext) => {
const promises = [];
promises.push(admin.firestore().doc(`Challenge/${change.data().challenge_id}`).update({replyCount: admin.firestore.FieldValue.increment(1)}))
promises.push(admin.firestore()
.collection(`User`)
.where('following', 'array-contains', change.data().user_id).get().then((snapshot: any) => {
if (!snapshot.empty) {
const batch = admin.firestore().batch();
snapshot.forEach((doc: any) => {
const tempObject = doc.data()
console.log(`/Subscribed_Challenges/${tempObject.userId}/myChallenges/${change.data().challenge_id}`)
const myChallenge = admin.firestore().doc(`/Subscribed_Challenges/${tempObject.userId}/myChallenges/${change.data().challenge_id}`)
batch.update(myChallenge, {replyCount: admin.firestore.FieldValue.increment(1)})
})
return batch.commit().catch((err: any) => {
console.log('Batch Error', err)
});
}
else {
return Promise.resolve()
}
}))
return Promise.all(promises)
.then(() => {
return "upvote complete";
})
})
If I correctly understand you code, you don't need to use Promise.all() but you need to correctly chain the different Promises returned by the asynchronous Firestore methods.
The following should do the trick (untested):
export const replyCreated = functions
.firestore
.document(`/Reply/{replyId}`)
.onCreate((change: any, context: functions.EventContext) => {
return admin.firestore().doc(`Challenge/${change.data().challenge_id}`).update({ replyCount: admin.firestore.FieldValue.increment(1) })
.then(() => {
return admin.firestore()
.collection(`User`)
.where('following', 'array-contains', change.data().user_id).get()
})
.then((snapshot: any) => {
if (!snapshot.empty) {
const batch = admin.firestore().batch();
snapshot.forEach((doc: any) => {
const tempObject = doc.data()
console.log(`/Subscribed_Challenges/${tempObject.userId}/myChallenges/${change.data().challenge_id}`)
const myChallenge = admin.firestore().doc(`/Subscribed_Challenges/${tempObject.userId}/myChallenges/${change.data().challenge_id}`)
batch.update(myChallenge, { replyCount: admin.firestore.FieldValue.increment(1) })
})
return batch.commit()
}
else {
throw new Error('Snapshot empty')
}
})
.catch((err: any) => {
console.log('Error', err);
return null;
});
})
You would use Promise.all() if you need to execute a number of asynchronous methods (which return a Promise) in parallel. In your case (if I am not mistaking) the only case where you need to execute asynchronous methods in parallel is in the block where you use the batched write, therefore the parallel execution is executed by the batched write itself. For the other methods, it is more a sequential execution and you have to chain the promises with the then() method.

Chai test for an error thrown inside of .then

When a query from salesforce comes back as an empty array, we catch that inside of .then() and throw error which I can console.log and see inside of .catch(). However I am having a hard time testing that error message.
I've tried chai-as-promise and to.eventually.equal('some string'), but came back as AssertionError: expected undefined to equal 'No campaigns for current period.'
cosnt campaignMember = {
getCampaignMembers: async () => {
await login();
return conn.sobject('CampaignMember')
.select('*')
.then((result) => {
if (!result[0]) {
throw Error('No campaigns for current period.');
}
return result;
})
.catch((err) => {
log.error(`Could not get paid current campaigns ${err}`);
});
},
}
module.exports = campaignMember
TEST
it('should pass', async () => {
await otherAsyncMethod();
await expect(campaignMember.getCampaignMembers(currentParent)).to.eventually.equal('No campaigns for current period.');
});
I want to be able to test the error message itself.
I found a solution through another stackoverflow article with this link to github issue comment. https://github.com/chaijs/chai/issues/882#issuecomment-322131680
I also had to remove catch from my async getCampaignMembers method.:
cosnt campaignMember = {
getCampaignMembers: async () => {
await login();
return conn.sobject('CampaignMember')
.select('*')
.then((result) => {
if (!result[0]) {
throw Error('No campaigns for current period.');
}
return result;
})
.catch(err => throw Error(err));
},
}
module.exports = campaignMember
TEST
it('should pass', async () => {
await otherAsyncMethod();
await campaignMember. getCampaignMembers(currentParent).catch((err) => {
expect(err).to.be.an('error').with.property('message', 'Error: No campaigns for current period.');
});
});

Can't access data base from a Firebase function

I tried everything , I have this cloud function (that otherwise works) :
exports.contentServer = functions.https.onRequest((request, response) => {
admin.database().ref('/list/' + "abc").once('value').then(function(snapshot) {
console.log(snapshot.val() );
return null;
}).catch(function(error) {
console.log("Error getting document:", error);
return response.send(error);
});
});
or also this :
admin.database().ref('/list').once('value').then(function(snapshot) {
var event = snapshot.val();
app.tell('Result: '+event);
});
and this :
exports.contentServer = functions.https.onRequest((request, response) => {
var db = admin.database();
db.ref("list/abc").once("value").then(snap => {
var store = snap.val().description;
return store;
}).then(() => {
var store = snap.val().description;
return store;
}).then(snap => {
var store = snap.val().description;
return store;
}).catch(err => {
console.log(err);
response.send("error occurred");
});
});
and always get back the error :
"Could not handle the request"
Or I get error on deploy that :
Each then() should return a value or throw
I have a collection called list, inside I have a document named "abc".
Is there something I have to include ? something I have to setup in Firebase to make it work ? anything basic nobody write on the docs ?
Modified following the comments above explaining the OP uses Firestore and not the Realtime Database
You should do as follows. You have to wait that the promise returned by the get() method resolves before sending back the response. For this you need to use the then() method, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
exports.contentServer = functions.https.onRequest((request, response) => {
admin.firestore().collection('list').doc('abc').get()
.then(docSnapshot => {
console.log(docSnapshot.data());
return response.send(docSnapshot.data()); // or any other value, like return response.send( {result: "success"} );
})
.catch(error => {
console.log("Error getting document:", error);
return response.status(500).send(error);
});
});
As written in the comments above, I would suggest that you watch the 3 videos about "JavaScript Promises" from the Firebase video series: https://firebase.google.com/docs/functions/video-series/
Try this
Updated. Return the response inside then() as what #Renaud Tarnec pointed out.
Using realtime database
exports.contentServer = functions.https.onRequest((request, response) => {
var database = admin.database();
database.ref('list').child('abc').once("value", snapshot => {
const data = snapshot.val();
return response.send(data);
}).catch(error => {
return response.status(500).send(error);
});
});
If you are using firestore.
exports.contentServer = functions.https.onRequest((request, response) => {
const firestore = admin.firestore();
firestore.collection("list").doc('abc').get().then(doc => {
console.log(doc.data());
return response.send(doc.data());
}).catch(error => {
return response.status(500).send(error);
});
});
Important: Don't forget to terminate the request by calling response.redirect(), response.send(), or responses.end() so you can avoid excessive charges from functions that run for too long

firebase pubsub function invoked but not writing to firestore

I have a simple pub sub cloud function
var serviceAccount = require("./serviceAccountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
exports.updateNews = functions.pubsub
.topic("firebase-schedule-cronForNews-us-central1")
.onPublish(message => {
axios
.get(
"https://newsapi.org/v2/top-headlines?apiKey=241414&sources=espn-cric-info"
)
.then(result => {
return result.data.articles.forEach(article => {
db.collection("news").add(article);
});
})
.then(result => {
console.log(result);
return result;
})
.catch(error => {
console.log(error);
return error;
});
return null;
});
The function is being invoked but it is not writing to firestore the same code works when I convert this to http function.
You may try returning the promises chain and using a batched write, as follows:
exports.updateNews = functions.pubsub
.topic("firebase-schedule-cronForNews-us-central1")
.onPublish(message => {
return axios // Note the return here
.get(
"https://newsapi.org/v2/top-headlines?apiKey=241414&sources=espn-cric-info"
)
.then(result => {
const batch = admin.firestore().batch();
result.data.articles.forEach(article => {
const docRef = admin.firestore().collection("news").doc();
batch.set(docRef, article);
});
return batch.commit();
})
.then(result => { // You don't need this then if you don't need the console.log
console.log(result);
return null;
});
});

Redux Thunk Common Actions - Standalone + Combined

Implementing the pattern Dan (#gaearon) demonstrated here, I was just wondering the best way to have the common action work standalone in the following scenario:
const commonAction = () => {
return async (dispatch, getState, api) => {
try {
const response = await api.get('/resource');
dispatch(success('SUCCESS', response.data));
} catch(error) {
dispatch(error('ERROR', error));
throw error; //this is the problem
}
}
const combinedAction = () => {
return async (dispatch, getState, api) => {
try {
await dispatch(commonAction());
const otherResponse = await api.get('/otherResource');
dispatch(success('COMBINED_SUCCESS', otherResponse.data));
} catch(error) {
dispatch(error('COMBINED_ERROR', error));
}
}
The above works in that if either fails, the COMBINED_ERROR will be dispatched, due to the commonAction re-throwing it's error. However, if I wanted to use commonAction by itself, I would get an Unhandled promise rejection error due to the re-throw.
One thought I had was an optional bool isStandAlone = true to determine whether to re-throw or not, i.e.
const commonAction = (isStandAlone = true) => {
return async (dispatch, getState, api) => {
try {
const response = await api.get('/resource');
dispatch(success('SUCCESS', response.data));
} catch(error) {
dispatch(error('ERROR', error));
if(!isStandAlone) {
throw error;
}
}
}
And then in the combinedAction I would just:
await dispatch(commonAction(false));
Is this a code smell? Is there a better and/or different way to approach this?

Resources