Erros comuns ao usar Node.js em Lambda

Eduardo Rabelo
3 min readMay 20, 2019

Já se passaram seis meses desde que a AWS Lambda adicionou suporte ao Node.js 8.10. Estou super feliz por finalmente poder utilizar async/await, simplificando minhas funções do Lambda.

Enquanto isso, ajudei alguns clientes com seus projetos serverless Node.js 8.10. Percebi alguns erros recorrentes ao utilizar async/await que vou compartilhar abaixo.

Ainda utilizando callbacks

Muitas pessoas ainda estão utilizando callbacks em suas funções async:

module.exports.handler = async (event, context, cb) => {
const response = {
statusCode: 200,
body: JSON.stringify({ message: 'hello world' })
}

cb(null, response)
}

a alternativa mais simples seria:

module.exports.handler = async (event, context) => {
const response = {
statusCode: 200,
body: JSON.stringify({ message: 'hello world' })
}

return response
}

Esquecer de usar util.promisify

Antes do Node.js 8.10, o pacote Bluebird preenchia uma lacuna enorme. Ele fornece utilitários para converter funções baseadas em callback para Promises. Mas o módulo nativo util do Node.js 8.10 preencheu essa lacuna com a função ‌promisify.

Por exemplo, agora podemos transformar a função readFile do módulo ‌fs da seguinte maneira:

const fs = require('fs')
const { promisify } = require('util')
const readFile = promisify(fs.readFile)

Não há mais necessidade de usar o Bluebird. Uma dependência a menos, o que ajuda a reduzir o tempo de inicialização frio (cold start) de nossas funções.

Código sequencial desnecessário

async/await permite escrever código assíncrono como se ele fosse síncrono, o que é incrível. Não precisamos lidar com callback hell!

Por outro lado, também podemos deixar passar uma vantagem de não executar tarefas simultaneamente, quando apropriado.

Veja o seguinte código como exemplo:

async function getFixturesAndTeam(teamId) {
const fixtures = await fixtureModel.fetchAll()
const team = await teamModel.fetch(teamId)
return {
team,
fixtures: fixtures.filter(x => x.teamId === teamId)
}
}

Esta função é fácil de ler, mas sua implementação dificilmente é a ideal. teamModel.fetch não depende do resultado fixtureModel.fetchAll, então eles devem ser executados simultaneamente.

Aqui está como você pode melhorar:

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)
}
}

Nesta versão, ambos fixtureModel.fetchAll e teamModel.fetch são iniciados simultaneamente.

Você também precisa estar atento ao usar map com async/await. O exemplo seguinte chamará cada teamModel.fetch sequencialmente:

async function getTeams(teamIds) {
const teams = _.map(teamIds, id => await teamModel.fetch(id))
return teams
}

Ao invés disso, podemos escrever:

async function getTeams(teamIds) {
const promises = _.map(teamIds, id => teamModel.fetch(id))
const teams = await Promise.all(promises)
return teams
}

No exemplo acima, mapeamos teamIds para um array de promises. Podemos então usar Promise.all para transformar esse array em uma única promise que retorna um array de equipes.

Nesse caso, teamModel.fetch é chamado simultaneamente e pode melhorar significativamente o tempo de execução.

async/await dentro de forEach

Este é um truque que pode pegar até desenvolvedores experientes em Node.js.

O problema é que código como esse não se comporta da maneira que você espera:

[ 1, 2, 3 ].forEach(async (x) => {
await sleep(x)
console.log(x)
})

console.log('all done.')

Quando você executar isso, você obterá a seguinte saída:

all done.

Você pode ver esses artigos (1, 2) para uma explicação mais detalhada sobre por que isso não funciona. Por enquanto, lembre-se de evitar usar async/await dentro de um forEach!

Esquecer de usar .promise() no AWS-SDK

Você sabia que os clientes do AWS SDK oferecem suporte a retornos de chamada com promises? Para usar async/await no AWS SDK, adicione .promise() aos métodos do cliente:

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()
}

Diga não aos callbacks, yay! 🎉

Finalizando

É isso aí! Esses são os erros mais comuns que podem ser evitados ao trabalhar com Node.js 8.10 em Lambda. Para obter mais dicas sobre como criar aplicativos serverless, escaláveis e prontos para produção, consulte o meu curso em vídeo! ;-)

Créditos ⭐️

--

--