Enter the Promise
A promise lets you respond to a unit of work that's executing asynchronously, such as a database write or a network request. Cloud Functions understand promises; if you want a function to stay alive during async work, you can do this by returning a promise from the function (except for HTTP/S triggers, which require a response sent to the client). When you return a promise, be sure to return a promise that is "resolved" after all asynchronous work performed in that function is fully complete. Many developers have found this video from the Firebase channel on YouTube very helpful for understanding how promises work with Cloud Functions:
In the video, Jen describes two main ways of dealing with promises. First, there's using the
then() method on a promise to chain items of work in sequence, where the next item of work depends on the results from the previous item. Second, there's
Promise.all() which creates a new promise that resolves after multiple items of work happening in parallel are all complete. Between these two mechanisms, you can manage most work that normally occurs in a function. When the work is complete, you return the final promise from the function so that the Cloud Functions environment knows when to clean up.
ECONNRESET got you down?
For simple functions, this works out pretty easily. But many of you (including myself) have discovered that complicated functions can be really difficult to manage when there's a lot of work (with lots of promises) going on. One of the symptoms of mismanaged promises is seeing the following error in the function log in the Firebase console:
Error: read ECONNRESET
This error could mean a few different things. In some cases it could be a bug in a client library. In other cases, it's a symptom of a common mistake when dealing with promises.
If a function is terminated before its network connections are finished, that means those open connections could be forced to close, leaving an
ECONNRESET in the log. Here's a situation when that can occur.
Imagine you want to start three items of async work, and the work is kicked off in a function called
doSomeAsync(), which returns a promise that's resolved upon completion, and you return the promise from the third task like this:
doSomeAsync(x) doSomeAsync(y) return doSomeAsync(z)
Returning a Promise from the function is good. But Cloud Functions needs a promise that resolves when all of the work is fully complete. If we don't have a guarantee that the last item of work will always finish after the other two, then Cloud Functions may clean up the function prematurely, causing problems. Instead, we need to combine all of the promises from all of the work into a new promise that resolves only after all of the others resolve, like this:
const promise_x = doSomeAsync(x) const promise_y = doSomeAsync(y) const promise_z = doSomeAsync(z) return Promise.all([promise_x, promise_y, promise_z])
If you're not tracking every single item of asynchronous work that you kick off in a function, you could see the dreaded
ECONNRESET error in your log, and things may not work the way you expect. It really can take some diligence to keep all your promises!
If you want to see an example of a more complicated function, I have an open source tic-tac-toe web game that I wrote for a session I gave at Google I/O '17 that talks about how it was built entirely with Firebase.