DEV Community

Cover image for Common Node8 mistakes in Lambda
We're Serverless! for Serverless Inc.

Posted on • Originally published at serverless.com

Common Node8 mistakes in Lambda

Originally posted at Serverless on October 4th, 2018

It’s been 6 months since AWS Lambda added support Node.js 8.10. I’m super happy that I can finally use async/await to simplify my Lambda functions.

In the meantime, I have helped a few clients with their Node8 serverless projects. In doing so I have seen some recurring mistakes around async/await.

Still using callbacks

Many people are still using the callbacks in their async handler functions:

module.exports.handler = async (event, context, cb) => {
const response = {
statusCode: 200,
body: JSON.stringify({ message: 'hello world' })
}
cb(null, response)
}
view raw .js hosted with ❤ by GitHub

instead of the simpler alternative:
module.exports.handler = async (event, context) => {
const response = {
statusCode: 200,
body: JSON.stringify({ message: 'hello world' })
}
return response
}
view raw .js hosted with ❤ by GitHub

Not using promisify

Before Node8, bluebird filled a massive gap. It provided the utility to convert callback-based functions to promise-based. But Node8’s built-in util module has filled that gap with the promisify function.

For example, we can now transform the readFile function from the fs module like this:

const fs = require('fs')
const { promisify } = require('util')
const readFile = promisify(fs.readFile)
view raw .js hosted with ❤ by GitHub

No need to use bluebird anymore. That’s one less dependency, which helps reduce the cold start time for our functions.

Too sequential

async/await lets you write asynchronous code as if they're synchronous, which is awesome. No more dealing with callback hell!

On the flip side, we can also miss a trick and not perform tasks concurrently where appropriate.

Take the following code as example:

async function getFixturesAndTeam(teamId) {
const fixtures = await fixtureModel.fetchAll()
const team = await teamModel.fetch(teamId)
return {
team,
fixtures: fixtures.filter(x => x.teamId === teamId)
}
}
view raw .js hosted with ❤ by GitHub

This function is easy to follow, but it’s hardly optimal. teamModel.fetch doesn't depend on the result of fixtureModel.fetchAll, so they should run concurrently.

Here is how you can improve it:

async function getFixturesAndTeam(teamId) {
const fixturesPromise = fixtureModel.fetchAll()
const teamPromise = teamModel.fetch(teamId)
const fixtures = await fixturesPromise
const team = await teamPromise
return {
team,
fixtures: fixtures.filter(x => x.teamId === teamId)
}
}
view raw .js hosted with ❤ by GitHub

In this version, both fixtureModel.fetchAll and teamModel.fetch are started concurrently.

You also need to watch out when using map with async/await. The following will call teamModel.fetch one after another:

async function getTeams(teamIds) {
const teams = _.map(teamIds, id => await teamModel.fetch(id))
return teams
}
view raw .js hosted with ❤ by GitHub

Instead, you should write it as the following:
async function getTeams(teamIds) {
const promises = _.map(teamIds, id => teamModel.fetch(id))
const teams = await Promise.all(promises)
return teams
}
view raw .js hosted with ❤ by GitHub

In this version we map teamIds to an array of Promise. We can then use Promise.all to turn this array into a single Promise that returns an array of teams.

In this case, teamModel.fetch is called concurrently and can significantly improve execution time.

async/await inside forEach()

This is a tricky one, and can sometimes catch out even experienced Node.js developers.

The problem is that code like this doesn’t behave the way you’d expect it to:

[ 1, 2, 3 ].forEach(async (x) => {
await sleep(x)
console.log(x)
})
console.log('all done.')
view raw .js hosted with ❤ by GitHub

When you run this you’ll get the following output:
all done.
view raw .js hosted with ❤ by GitHub

See this post for a longer explanation about why this doesn’t work. For now, just remember to avoid using async/await inside a forEach!

Not using AWSSDK’s .promise()

Did you know that the AWS SDK clients support both callbacks and promises? To use async/await with the AWS SDK, add .promise() to client methods like this:

const AWS = require('aws-sdk')
const Lambda = new AWS.Lambda()
async function invokeLambda(functionName) {
const req = {
FunctionName: functionName,
Payload: JSON.stringify({ message: 'hello world' })
}
await Lambda.invoke(req).promise()
}
view raw .js hosted with ❤ by GitHub

No more callback functions, yay!

Wrap-up

That’s it, 5 common mistakes to avoid when working with Node.js 8.10 in Lambda. For more tips on building production-ready serverless applications and operational best practices, check out my video course. ;-)

Further reading:

Originally published at https://www.serverless.com.

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free →

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay