DOUG STEVENSON: Hey, welcome back
to another Firecast about Cloud Functions for Firebase.
My name is Doug Stevenson, and today's topic
is programming with JavaScript Promises in a Firestore Trigger
on Cloud Functions.
In the last video, I talked about using promises
in an HTTP trigger.
If you haven't watched that video,
do it first, and come back here when you're done.
I'll wait.
So you learned last time that a JavaScript promise represents
some asynchronous work going on in the background
of your function.
It has three states--
pending, fulfilled, and rejected.
And you can write callbacks using the Then and Catch
methods on a promise to know when the state changes.
You also learned the first rule for terminating a cloud
function--
HTTP triggers must send a response to the client
to terminate properly.
Today we'll look at the second rule-- for all other types
of triggers, you must return a promise
that becomes fulfilled or rejected
when all the pending work in that function is complete.
This lets Cloud Functions know when
it's safe to clean up the function invocation
and move on to the next one.
Last time, I wrote an HTTP function
that fetches a document from Firestore,
and it sends its contents to the client like a simple web API.
This time, I'm going to write a Firestore trigger that
fires whenever that document gets updated with new data.
If you recall, my Firestore database looks like this.
In the Firebase console, you can see
I have a collection called Cities Weather, where
each document tracks the weather conditions over time in a city.
As I mentioned in the last video,
I'm going to Boston soon, so I made an HTTP API endpoint
that my app can call to get the latest conditions.
Any app can poll this location to check for updates
to see if something changed with the weather conditions
in Boston.
This is OK for general use by anyone
who knows the HTTP endpoint.
But I have my own Firebase app connected to this project,
and I don't want my app to keep polling this endpoint
repeatedly to get updates.
There is a better way to do this.
What I want to do instead of polling
is write a Firestore trigger that
fires in response to updates in the Boston weather document.
With each update, I'll use Cloud Messaging
to send a notification to my app to indicate
a change in weather conditions.
This is far more efficient than repeatedly polling
the HTTP endpoint or even querying the document directly.
And my app can always stay up to date with the latest weather
data, even when it's not running.
Let's see how to do that.
Here's the HTTP function from last time
that can be polled for weather updates in Boston.
What I'll do here is write a new Firestore
trigger that fires in response to updates to that document.
I'll export it and call it onBostonWeatherUpdate.
Then I'll use the Functions SDK to create
a Firestore triggered that responds to updates
in the Boston weather document.
I'll pass a function to the onUpdate method,
which will be invoked whenever there's
a change in this document.
The function accepts a change parameter
that describes the update that happened.
The change parameter has two properties-- before and after.
I'm interested in the contents of the document
after the change occurred.
This after property is a document snapshot
with the updated contents of that document,
so I use its data method to convert it
to a JavaScript object.
Now that I have the document data, what I want to do
is build an FCM message payload to send to my app.
I'll paste some code here that I already wrote.
FCM requires all the values to be strings,
so I'm converting any numbers to strings as needed.
Now, I'll use the Admin SDK to send
this data payload to a topic called a weather_boston.
This lets every installation of my app
receive these notifications if it uses the FCM
SDK to subscribe to this topic.
If you're not familiar with FCM, that's OK.
You can learn all about that by clicking the links
in the description below.
It's a great way to keep your app up
to date with changes in your database.
But there's one thing I forgot in my code.
Remember the second rule for terminating all functions other
than HTTP triggers-- they must return a promise that's
fulfilled or rejected when all the pending work is complete.
What I forgot to check in my code
is what the Admin SDK returns when I sent that message.
So let's go see it in VS Code.
If I hover the mouse over sendToTopic,
VS Code will tell me its arguments and return value.
And I can see here that it returns a promise.
That means this function returns immediately,
but it isn't finished until the promise is
fulfilled or rejected.
So what I can do here is return this promise from my function,
and that will make Cloud Functions
wait until FCM is done before cleaning up and moving on.
OK, that's better, but there's still one thing to consider.
Remember that every promise either
becomes fulfilled successfully or rejected with an error.
So it's a good idea to think about
if you need to handle any error conditions when
you're dealing with a promise.
If you remember from the last video,
you can use the Catch method on a promise to trap any errors.
What I'll do is call the Catch method on the promise return
by sendToTopic.
I'll pass it a callback function that
handles the error condition.
For now, I'll just log it.
And also note the Catch itself returns
a promise when its callback is complete,
which is what this entire function is actually
returning now.
But if all you're going to do is log the error,
there's no reason to use Catch here.
If I just return the original promise as before,
Cloud Functions will log the error
if the promise is rejected.
In that function, if I didn't return a promise that's
fulfilled when the admin SDK is done sending the message
asynchronously, it's very possible
that the message would never be sent,
because Cloud Functions could clean up
before the work is done.
Forgetting to return a promise can lead to inconsistent bugs
in your background function.
You should always track the promises returned by APIs
you call in your functions.
That way you always return a promise from your background
function.
This includes functions triggered
not only for changes in Firestore,
but also in Realtime Database, Cloud Storage, Authentication,
Analytics, Crashlytics, and Pub/Sub,
and possibly more to come.
You should know there's still more to learn about promises.
Next time, I'll talk about what you
can do with promises to manage more complicated work
in your function code.
So subscribe here to the Firebase channel on YouTube
to get notified of that.
Until then, check out some of the links in the documentation
in the description below.
And I'll see you here next time.
[MUSIC PLAYING]