Avoiding GCF anti-patterns part 4: How to handle Promises correctly in your Node.js Cloud Function
Sara Ford
Senior Developer Relations Engineer
Martin Skoviera
Technical Solutions Engineer
Try Google Cloud
Start building on Google Cloud with $300 in free credits and 20+ always free products.
Free trialEditor's note: Over the next several weeks, you'll see a series of blog posts focusing on best practices for writing Google Cloud Functions based on common questions or misconceptions as seen by the Support team. We refer to these as "anti-patterns" and offer you ways to avoid them. This article is the fourth post in the series.
Scenario
You notice that the data your Function saves to a database is either "undefined" or it is saving a cached value. For example, you have a Cloud Task that every hour invokes a Cloud Function that retrieves data from one database, transforms that data, and then saves the modified data to another database. Yet, you notice that your data is either undefined or a cached value.
Most common root issue
An unhandled promise.
One common anti-pattern we notice when using the then-able approach in a Cloud Function is that it is easy to overlook an unhandled Promise. For example, can you spot the issue in the following example?
You will not know how long the call transformData(response.data)
will take. And more likely, this call is probably an async method. The result is that the saveDataToDatabase
method is executed before transformData
()
has completed. Thus, the variable dataNowTransformed
is undefined upon saving to the database.
How to investigate
- Are you using
await
? If not, we recommend using async and await keywords to help improve the readability of your code. - If you cannot convert to await at this time, you'll need to add a logging layer (see example below) to determine if you have an unhandled promise.
How to add a logging layer using then-able functions
There are two ways to log:
- sync logging by modifying your callbacks
- async logging to avoid modifying your callbacks (i.e. adding a logging layer using then-able functions)
Suppose you are okay with modifying your callbacks. You can add a synchronous logging layer just by using console.log()
. We recommend creating a synchronous method called logData
()
function to keep your code clean and avoid numerous console.log
()
statements throughout your code.
where logData()
looks like:
Now suppose you do not want to modify the code within your callbacks. We recommend adding an async logging method as follows:
1. Create an async logDataAsync()
method in your Function.
2. Call the logDataAsync
method using the then-able() approach
Please see the helpful tips section for a more compact way to apply the async logging approach.
How to handle the Promise using the then-able approach
We recommend that you perform one task at a time within a .then()
callback. Going back to our original anti-pattern example, let's update it to use the then-able approach. Here's an end-to-end working example:
But all these back to back .then()
calls (called Promise chaining) make the code difficult to read and maintain. Please see the helpful tips section for a more compact way to write this code, in case you see it elsewhere.
If possible, we suggest that you use awaits. Notice how the code in the Function event handler callToSlowRespondingAPI
now becomes more succinct and readable. In addition, if anything goes wrong in these async method calls, an exception is thrown, in lieu of returning null or false in the return statement.
Other helpful tips
- Are you testing locally using the Functions Framework? You can follow this codelab to learn how to debug Node.js functions locally in Visual Studio Code.
- Whenever you are logging data, be aware of how much data you are logging (see log size limits) and whether your data has any sensitive information or personally identifiable information.
- Using the then-able() approach, you might often see code written as follows. This is functionally equivalent to the longer then-able() Promise-chaining version used above. However, we recommend using the async await approach for readability.