Como Escrever Código Assíncrono no Node.js

O autor selecionou o Fundo da Internet Aberta/Liberdade de Expressão para receber uma doação como parte do programa Escreva para Doações.

Introdução

Para muitos programas em JavaScript, o código é executado conforme o desenvolvedor o escreve – linha por linha. Isso é chamado de execução síncrona, porque as linhas são executadas uma após a outra, na ordem em que foram escritas. No entanto, nem toda instrução que você dá ao computador precisa ser atendida imediatamente. Por exemplo, se você enviar uma solicitação de rede, o processo que executa seu código terá que esperar os dados retornarem antes de poder trabalhar com eles. Nesse caso, tempo seria desperdiçado se não executasse outro código enquanto esperava a solicitação de rede ser concluída. Para resolver esse problema, os desenvolvedores usam programação assíncrona, na qual as linhas de código são executadas em uma ordem diferente daquela em que foram escritas. Com a programação assíncrona, podemos executar outro código enquanto esperamos por atividades longas, como solicitações de rede, para terminar.

O código JavaScript é executado em uma única thread dentro de um processo de computador. Seu código é processado de forma síncrona nesta thread, com apenas uma instrução executada de cada vez. Portanto, se quisermos fazer uma tarefa demorada nesta thread, todo o restante do código é bloqueado até que a tarefa esteja completa. Ao aproveitar os recursos de programação assíncrona do JavaScript, podemos transferir tarefas demoradas para uma thread em segundo plano para evitar esse problema. Quando a tarefa estiver completa, o código necessário para processar os dados da tarefa é colocado de volta na única thread principal.

Neste tutorial, você aprenderá como o JavaScript gerencia tarefas assíncronas com a ajuda do Loop de Eventos, que é uma construção do JavaScript que conclui uma nova tarefa enquanto espera por outra. Você então criará um programa que usa programação assíncrona para solicitar uma lista de filmes de uma API do Studio Ghibli e salvar os dados em um arquivo CSV. O código assíncrono será escrito de três maneiras: callbacks, promessas e com as palavras-chave async/await.

Observação: Até a data desta escrita, a programação assíncrona não é mais feita apenas usando callbacks, mas aprender este método obsoleto pode fornecer um ótimo contexto sobre por que a comunidade JavaScript agora usa promessas. As palavras-chave async/await nos permitem usar promessas de uma maneira menos verbosa, sendo assim a forma padrão de fazer programação assíncrona em JavaScript no momento da escrita deste artigo.

Pré-requisitos

O Loop de Eventos

Vamos começar estudando o funcionamento interno da execução de funções em JavaScript. Compreender como isso se comporta permitirá que você escreva código assíncrono de forma mais deliberada e o ajudará a solucionar problemas de código no futuro.

Ao executar o código, o interpretador JavaScript adiciona cada função chamada à pilha de chamadas do JavaScript. A pilha de chamadas é uma pilha – uma estrutura de dados semelhante a uma lista onde os itens só podem ser adicionados no topo e removidos do topo. As pilhas seguem o princípio “Último a entrar, primeiro a sair” ou LIFO. Se você adicionar dois itens na pilha, o item mais recentemente adicionado é removido primeiro.

Vamos ilustrar com um exemplo usando a pilha de chamadas. Se o JavaScript encontrar uma função functionA() sendo chamada, ela é adicionada à pilha de chamadas. Se essa função functionA() chamar outra função functionB(), então functionB() é adicionada ao topo da pilha de chamadas. À medida que o JavaScript completa a execução de uma função, ela é removida da pilha de chamadas. Portanto, o JavaScript executará functionB() primeiro, removerá da pilha quando estiver completo e, em seguida, terminará a execução de functionA() e a removerá da pilha de chamadas. É por isso que as funções internas são sempre executadas antes de suas funções externas.

Quando o JavaScript encontra uma operação assíncrona, como escrever em um arquivo, ele a adiciona a uma tabela em sua memória. Esta tabela armazena a operação, a condição para que ela seja concluída e a função a ser chamada quando ela for concluída. À medida que a operação é concluída, o JavaScript adiciona a função associada à fila de mensagens. Uma fila é outra estrutura de dados semelhante a uma lista onde os itens só podem ser adicionados na parte inferior, mas removidos no topo. Na fila de mensagens, se duas ou mais operações assíncronas estiverem prontas para que suas funções sejam executadas, a operação assíncrona que foi concluída primeiro terá sua função marcada para execução primeiro.

Funções na fila de mensagens estão aguardando para serem adicionadas à pilha de chamadas. O loop de eventos é um processo perpétuo que verifica se a pilha de chamadas está vazia. Se estiver, então o primeiro item na fila de mensagens é movido para a pilha de chamadas. O JavaScript prioriza funções na fila de mensagens sobre chamadas de função que ele interpreta no código. O efeito combinado da pilha de chamadas, fila de mensagens e loop de eventos permite que o código JavaScript seja processado enquanto gerencia atividades assíncronas.

Agora que você tem uma compreensão de alto nível do loop de eventos, você sabe como o código assíncrono que você escreve será executado. Com este conhecimento, você agora pode criar código assíncrono com três abordagens diferentes: callbacks, promises e async/await.

Programação Assíncrona com Callbacks

A callback function is one that is passed as an argument to another function, and then executed when the other function is finished. We use callbacks to ensure that code is executed only after an asynchronous operation is completed.

Por muito tempo, os retornos de chamada foram o mecanismo mais comum para escrever código assíncrono, mas agora eles em grande parte se tornaram obsoletos porque podem tornar o código confuso para ler. Neste passo, você escreverá um exemplo de código assíncrono usando retornos de chamada para que você possa usá-lo como linha de base para ver a eficiência aumentada de outras estratégias.

Há muitas maneiras de usar funções de retorno de chamada em outra função. Geralmente, elas seguem esta estrutura:

function asynchronousFunction([ Function Arguments ], [ Callback Function ]) {
    [ Action ]
}

Embora não seja sintaticamente necessário pelo JavaScript ou Node.js ter a função de retorno de chamada como o último argumento da função externa, é uma prática comum que facilita a identificação dos retornos de chamada. Também é comum os desenvolvedores JavaScript usarem uma função anônima como retorno de chamada. Funções anônimas são aquelas criadas sem um nome. Geralmente é muito mais legível quando uma função é definida no final da lista de argumentos.

Para demonstrar retornos de chamada, vamos criar um módulo Node.js que escreve uma lista de filmes do Studio Ghibli em um arquivo. Primeiro, crie uma pasta que armazenará nosso arquivo JavaScript e sua saída:

  1. mkdir ghibliMovies

Em seguida, entre nessa pasta:

  1. cd ghibliMovies

Começaremos fazendo uma solicitação HTTP para a API do Studio Ghibli, que nossa função de retorno de chamada irá registrar os resultados. Para fazer isso, instalaremos uma biblioteca que nos permite acessar os dados de uma resposta HTTP em um retorno de chamada.

No seu terminal, inicialize o npm para que possamos ter uma referência para nossos pacotes posteriormente:Em seguida, instale a biblioteca request:

  1. npm init -y

Então, instale a biblioteca request:

  1. npm i request --save

Agora abra um novo arquivo chamado callbackMovies.js em um editor de texto como nano:

  1. nano callbackMovies.js

No seu editor de texto, insira o seguinte código. Vamos começar enviando uma requisição HTTP com o módulo request:

callbackMovies.js
const request = require('request');

request('https://ghibliapi.herokuapp.com/films');

Na primeira linha, carregamos o módulo request que foi instalado via npm. O módulo retorna uma função que pode fazer requisições HTTP; então, salvamos essa função na constante request.

Em seguida, fazemos a requisição HTTP usando a função request(). Agora vamos imprimir os dados da requisição HTTP no console adicionando as alterações destacadas:

callbackMovies.js
const request = require('request');

request('https://ghibliapi.herokuapp.com/films', (error, response, body) => {
    if (error) {
        console.error(`Could not send request to API: ${error.message}`);
        return;
    }

    if (response.statusCode != 200) {
        console.error(`Expected status code 200 but received ${response.statusCode}.`);
        return;
    }

    console.log('Processing our list of movies');
    movies = JSON.parse(body);
    movies.forEach(movie => {
        console.log(`${movie['title']}, ${movie['release_date']}`);
    });
});

Ao usarmos a função request(), damos a ela dois parâmetros:

  • A URL do site que estamos tentando solicitar
  • A callback function that handles any errors or successful responses after the request is complete

Nossa função de callback tem três argumentos: error, response e body. Quando a requisição HTTP estiver completa, os argumentos recebem automaticamente valores dependendo do resultado. Se a requisição falhar ao ser enviada, então error conteria um objeto, mas response e body seriam null. Se a requisição for bem-sucedida, então a resposta HTTP é armazenada em response. Se nossa resposta HTTP retornar dados (neste exemplo, recebemos JSON), então os dados são definidos em body.

Nossa função de retorno primeiro verifica se recebemos um erro. É uma boa prática verificar erros em um retorno primeiro para que a execução do retorno não continue com dados ausentes. Neste caso, registramos o erro e a execução da função. Em seguida, verificamos o código de status da resposta. Nosso servidor nem sempre pode estar disponível e as APIs podem mudar, fazendo com que solicitações antes sensatas se tornem incorretas. Verificando que o código de status é 200, o que significa que a solicitação foi “OK”, podemos ter confiança de que nossa resposta é o que esperamos que seja.

Finalmente, analisamos o corpo da resposta para um Array e iteramos por cada filme para registrar seu nome e ano de lançamento.

Após salvar e sair do arquivo, execute este script com:

  1. node callbackMovies.js

Você obterá a seguinte saída:

Output
Castle in the Sky, 1986 Grave of the Fireflies, 1988 My Neighbor Totoro, 1988 Kiki's Delivery Service, 1989 Only Yesterday, 1991 Porco Rosso, 1992 Pom Poko, 1994 Whisper of the Heart, 1995 Princess Mononoke, 1997 My Neighbors the Yamadas, 1999 Spirited Away, 2001 The Cat Returns, 2002 Howl's Moving Castle, 2004 Tales from Earthsea, 2006 Ponyo, 2008 Arrietty, 2010 From Up on Poppy Hill, 2011 The Wind Rises, 2013 The Tale of the Princess Kaguya, 2013 When Marnie Was There, 2014

Recebemos com sucesso uma lista de filmes do Studio Ghibli com o ano de lançamento. Agora vamos completar este programa escrevendo a lista de filmes que estamos registrando atualmente em um arquivo.

Atualize o arquivo callbackMovies.js em seu editor de texto para incluir o seguinte código destacado, que cria um arquivo CSV com nossos dados de filmes:

callbackMovies.js
const request = require('request');
const fs = require('fs');

request('https://ghibliapi.herokuapp.com/films', (error, response, body) => {
    if (error) {
        console.error(`Could not send request to API: ${error.message}`);
        return;
    }

    if (response.statusCode != 200) {
        console.error(`Expected status code 200 but received ${response.statusCode}.`);
        return;
    }

    console.log('Processing our list of movies');
    movies = JSON.parse(body);
    let movieList = '';
    movies.forEach(movie => {
        movieList += `${movie['title']}, ${movie['release_date']}\n`;
    });

    fs.writeFile('callbackMovies.csv', movieList, (error) => {
        if (error) {
            console.error(`Could not save the Ghibli movies to a file: ${error}`);
            return;
        }

        console.log('Saved our list of movies to callbackMovies.csv');;
    });
});

Observando as mudanças destacadas, vemos que importamos o módulo fs. Este módulo é padrão em todas as instalações do Node.js e contém um método writeFile() que pode escrever assincronamente em um arquivo.

Em vez de registrar os dados no console, agora os adicionamos a uma variável de string movieList. Em seguida, usamos writeFile() para salvar o conteúdo de movieList em um novo arquivo — callbackMovies.csv. Finalmente, fornecemos um retorno de chamada para a função writeFile(), que possui um argumento: error. Isso nos permite lidar com casos em que não podemos escrever em um arquivo, por exemplo, quando o usuário em que estamos executando o processo node não possui essas permissões.

Salve o arquivo e execute este programa Node.js novamente com:

  1. node callbackMovies.js

No seu diretório ghibliMovies, você verá callbackMovies.csv, que tem o seguinte conteúdo:

callbackMovies.csv
Castle in the Sky, 1986
Grave of the Fireflies, 1988
My Neighbor Totoro, 1988
Kiki's Delivery Service, 1989
Only Yesterday, 1991
Porco Rosso, 1992
Pom Poko, 1994
Whisper of the Heart, 1995
Princess Mononoke, 1997
My Neighbors the Yamadas, 1999
Spirited Away, 2001
The Cat Returns, 2002
Howl's Moving Castle, 2004
Tales from Earthsea, 2006
Ponyo, 2008
Arrietty, 2010
From Up on Poppy Hill, 2011
The Wind Rises, 2013
The Tale of the Princess Kaguya, 2013
When Marnie Was There, 2014

É importante notar que escrevemos no nosso arquivo CSV no retorno de chamada da solicitação HTTP. Uma vez que o código está na função de retorno de chamada, ele só escreverá no arquivo após a conclusão da solicitação HTTP. Se quiséssemos comunicar com um banco de dados depois de escrever nosso arquivo CSV, faríamos outra função assíncrona que seria chamada no retorno de chamada de writeFile(). Quanto mais código assíncrono tivermos, mais funções de retorno de chamada terão que ser aninhadas.

Vamos imaginar que queremos executar cinco operações assíncronas, sendo que cada uma só pode ser executada quando outra estiver completa. Se fossemos codificar isso, teríamos algo assim:

doSomething1(() => {
    doSomething2(() => {
        doSomething3(() => {
            doSomething4(() => {
                doSomething5(() => {
                    // ação final
                });
            });
        }); 
    });
});

Quando os callbacks aninhados têm muitas linhas de código para executar, eles se tornam substancialmente mais complexos e difíceis de ler. Conforme seu projeto JavaScript cresce em tamanho e complexidade, esse efeito se tornará mais pronunciado, até que eventualmente se torne impossível de gerenciar. Por causa disso, os desenvolvedores não usam mais callbacks para lidar com operações assíncronas. Para melhorar a sintaxe do nosso código assíncrono, podemos usar promises em vez disso.

Usando Promises para Programação Assíncrona Concisa

A promise is a JavaScript object that will return a value at some point in the future. Asynchronous functions can return promise objects instead of concrete values. If we get a value in the future, we say that the promise was fulfilled. If we get an error in the future, we say that the promise was rejected. Otherwise, the promise is still being worked on in a pending state.

As promises geralmente seguem a seguinte forma:

promiseFunction()
    .then([ Callback Function for Fulfilled Promise ])
    .catch([ Callback Function for Rejected Promise ])

Como mostrado neste modelo, as promises também usam funções de retorno de chamada. Temos uma função de retorno de chamada para o método then(), que é executado quando uma promise é cumprida. Também temos uma função de retorno de chamada para o método catch() para lidar com quaisquer erros que surjam durante a execução da promise.

Vamos obter experiência em primeira mão com promises reescrevendo nosso programa Studio Ghibli para usar promises em vez disso.

Axios é um cliente HTTP baseado em promises para JavaScript, então vamos em frente e instalá-lo:

  1. npm i axios --save

Agora, com o seu editor de texto de escolha, crie um novo arquivo promiseMovies.js:

  1. nano promiseMovies.js

Nosso programa fará uma requisição HTTP com o axios e então usará uma versão especial baseada em promises do fs para salvar em um novo arquivo CSV.

Digite este código em promiseMovies.js para que possamos carregar o Axios e enviar uma solicitação HTTP para a API de filmes:

promiseMovies.js
const axios = require('axios');

axios.get('https://ghibliapi.herokuapp.com/films');

Na primeira linha, carregamos o módulo axios, armazenando a função retornada em uma constante chamada axios. Em seguida, usamos o método axios.get() para enviar uma solicitação HTTP para a API.

O método axios.get() retorna uma promessa. Vamos encadear essa promessa para que possamos imprimir a lista de filmes do Ghibli no console:

promiseMovies.js
const axios = require('axios');
const fs = require('fs').promises;


axios.get('https://ghibliapi.herokuapp.com/films')
    .then((response) => {
        console.log('Successfully retrieved our list of movies');
        response.data.forEach(movie => {
            console.log(`${movie['title']}, ${movie['release_date']}`);
        });
    })

Vamos analisar o que está acontecendo. Após fazer uma solicitação GET HTTP com axios.get(), usamos a função then(), que é executada apenas quando a promessa é cumprida. Neste caso, imprimimos os filmes na tela como fizemos no exemplo de callbacks.

Para melhorar este programa, adicione o código destacado para escrever os dados HTTP em um arquivo:

promiseMovies.js
const axios = require('axios');
const fs = require('fs').promises;


axios.get('https://ghibliapi.herokuapp.com/films')
    .then((response) => {
        console.log('Successfully retrieved our list of movies');
        let movieList = '';
        response.data.forEach(movie => {
            movieList += `${movie['title']}, ${movie['release_date']}\n`;
        });

        return fs.writeFile('promiseMovies.csv', movieList);
    })
    .then(() => {
        console.log('Saved our list of movies to promiseMovies.csv');
    })

Adicionalmente, importamos novamente o módulo fs. Observe como após a importação do fs temos .promises. O Node.js inclui uma versão baseada em promessas da biblioteca fs baseada em callbacks, então a compatibilidade com versões anteriores não é quebrada em projetos legados.

A primeira função then() que processa a solicitação HTTP agora chama fs.writeFile() em vez de imprimir no console. Como importamos a versão baseada em promessas do fs, nossa função writeFile() retorna outra promessa. Assim, anexamos outra função then() para quando a promessa writeFile() for cumprida.

A promise can return a new promise, allowing us to execute promises one after the other. This paves the way for us to perform multiple asynchronous operations. This is called promise chaining, and it is analogous to nesting callbacks. The second then() is only called after we successfully write to the file.

Nota: Neste exemplo, não verificamos o código de status HTTP como fizemos no exemplo de retorno de chamada. Por padrão, o axios não cumpre sua promessa se receber um código de status indicando um erro. Como tal, não precisamos mais validá-lo.

Para completar este programa, encadeie a promessa com uma função catch() como destacado a seguir:

promiseMovies.js
const axios = require('axios');
const fs = require('fs').promises;


axios.get('https://ghibliapi.herokuapp.com/films')
    .then((response) => {
        console.log('Successfully retrieved our list of movies');
        let movieList = '';
        response.data.forEach(movie => {
            movieList += `${movie['title']}, ${movie['release_date']}\n`;
        });

        return fs.writeFile('promiseMovies.csv', movieList);
    })
    .then(() => {
        console.log('Saved our list of movies to promiseMovies.csv');
    })
    .catch((error) => {
        console.error(`Could not save the Ghibli movies to a file: ${error}`);
    });

Se alguma promessa não for cumprida na cadeia de promessas, o JavaScript automaticamente vai para a função catch() se ela foi definida. É por isso que temos apenas uma cláusula catch() mesmo que tenhamos duas operações assíncronas.

Vamos confirmar que nosso programa produz a mesma saída executando:

  1. node promiseMovies.js

No seu diretório ghibliMovies, você verá o arquivo promiseMovies.csv contendo:

promiseMovies.csv
Castle in the Sky, 1986
Grave of the Fireflies, 1988
My Neighbor Totoro, 1988
Kiki's Delivery Service, 1989
Only Yesterday, 1991
Porco Rosso, 1992
Pom Poko, 1994
Whisper of the Heart, 1995
Princess Mononoke, 1997
My Neighbors the Yamadas, 1999
Spirited Away, 2001
The Cat Returns, 2002
Howl's Moving Castle, 2004
Tales from Earthsea, 2006
Ponyo, 2008
Arrietty, 2010
From Up on Poppy Hill, 2011
The Wind Rises, 2013
The Tale of the Princess Kaguya, 2013
When Marnie Was There, 2014

Com promessas, podemos escrever um código muito mais conciso do que usando apenas retornos de chamada. A cadeia de promessas é uma opção mais limpa do que aninhar retornos de chamada. No entanto, à medida que fazemos mais chamadas assíncronas, nossa cadeia de promessas se torna mais longa e mais difícil de manter.

A verbosidade de retornos de chamada e promessas vem da necessidade de criar funções quando temos o resultado de uma tarefa assíncrona. Uma experiência melhor seria esperar por um resultado assíncrono e colocá-lo em uma variável fora da função. Dessa forma, podemos usar os resultados nas variáveis sem precisar fazer uma função. Podemos alcançar isso com as palavras-chave async e await.

Escrevendo JavaScript com async/await

As palavras-chave async/await fornecem uma sintaxe alternativa ao trabalhar com promessas. Em vez de ter o resultado de uma promessa disponível no método then(), o resultado é retornado como um valor, como em qualquer outra função. Definimos uma função com a palavra-chave async para informar ao JavaScript que é uma função assíncrona que retorna uma promessa. Usamos a palavra-chave await para dizer ao JavaScript para retornar os resultados da promessa em vez de retornar a própria promessa quando ela for cumprida.

Em geral, o uso de async/await parece com isso:

async function() {
    await [Asynchronous Action]
}

Vamos ver como usar async/await pode melhorar nosso programa Studio Ghibli. Use seu editor de texto para criar e abrir um novo arquivo asyncAwaitMovies.js:

  1. nano asyncAwaitMovies.js

No seu novo arquivo JavaScript aberto, vamos começar importando os mesmos módulos que usamos em nosso exemplo de promessa:

asyncAwaitMovies.js
const axios = require('axios');
const fs = require('fs').promises;

As importações são as mesmas que em promiseMovies.js porque async/await utiliza promessas.

Agora usamos a palavra-chave async para criar uma função com nosso código assíncrono:

asyncAwaitMovies.js
const axios = require('axios');
const fs = require('fs').promises;

async function saveMovies() {}

Criamos uma nova função chamada saveMovies(), mas incluímos async no início de sua definição. Isso é importante, pois só podemos usar a palavra-chave await em uma função assíncrona.

Use a palavra-chave await para fazer uma solicitação HTTP que obtenha a lista de filmes da API do Ghibli:

asyncAwaitMovies.js
const axios = require('axios');
const fs = require('fs').promises;

async function saveMovies() {
    let response = await axios.get('https://ghibliapi.herokuapp.com/films');
    let movieList = '';
    response.data.forEach(movie => {
        movieList += `${movie['title']}, ${movie['release_date']}\n`;
    });
}

Em nossa função saveMovies(), fazemos uma solicitação HTTP com axios.get() como antes. Desta vez, não encadeamos com uma função then(). Em vez disso, adicionamos await antes de sua chamada. Quando o JavaScript vê await, ele só executará o código restante da função após axios.get() terminar a execução e definir a variável response. O outro código salva os dados do filme para que possamos escrever em um arquivo.

Vamos escrever os dados do filme em um arquivo:

asyncAwaitMovies.js
const axios = require('axios');
const fs = require('fs').promises;

async function saveMovies() {
    let response = await axios.get('https://ghibliapi.herokuapp.com/films');
    let movieList = '';
    response.data.forEach(movie => {
        movieList += `${movie['title']}, ${movie['release_date']}\n`;
    });
    await fs.writeFile('asyncAwaitMovies.csv', movieList);
}

Também usamos a palavra-chave await ao escrever no arquivo com fs.writeFile().

Para concluir esta função, precisamos capturar os erros que nossas promessas podem lançar. Vamos fazer isso encapsulando nosso código em um bloco try/catch:

asyncAwaitMovies.js
const axios = require('axios');
const fs = require('fs').promises;

async function saveMovies() {
    try {
        let response = await axios.get('https://ghibliapi.herokuapp.com/films');
        let movieList = '';
        response.data.forEach(movie => {
            movieList += `${movie['title']}, ${movie['release_date']}\n`;
        });
        await fs.writeFile('asyncAwaitMovies.csv', movieList);
    } catch (error) {
        console.error(`Could not save the Ghibli movies to a file: ${error}`);
    }
}

Como as promessas podem falhar, envolvemos nosso código assíncrono com uma cláusula try/catch. Isso capturará quaisquer erros que forem lançados quando as operações de solicitação HTTP ou escrita de arquivo falharem.

Por fim, vamos chamar nossa função assíncrona saveMovies() para que ela seja executada quando executarmos o programa com node.

asyncAwaitMovies.js
const axios = require('axios');
const fs = require('fs').promises;

async function saveMovies() {
    try {
        let response = await axios.get('https://ghibliapi.herokuapp.com/films');
        let movieList = '';
        response.data.forEach(movie => {
            movieList += `${movie['title']}, ${movie['release_date']}\n`;
        });
        await fs.writeFile('asyncAwaitMovies.csv', movieList);
    } catch (error) {
        console.error(`Could not save the Ghibli movies to a file: ${error}`);
    }
}

saveMovies();

Num relance, isso parece um bloco de código JavaScript síncrono típico. Tem menos funções sendo passadas, o que parece um pouco mais organizado. Esses pequenos ajustes tornam o código assíncrono com async/await mais fácil de manter.

Teste esta iteração do nosso programa inserindo isso no seu terminal:

  1. node asyncAwaitMovies.js

Na sua pasta ghibliMovies, um novo arquivo asyncAwaitMovies.csv será criado com o seguinte conteúdo:

asyncAwaitMovies.csv
Castle in the Sky, 1986
Grave of the Fireflies, 1988
My Neighbor Totoro, 1988
Kiki's Delivery Service, 1989
Only Yesterday, 1991
Porco Rosso, 1992
Pom Poko, 1994
Whisper of the Heart, 1995
Princess Mononoke, 1997
My Neighbors the Yamadas, 1999
Spirited Away, 2001
The Cat Returns, 2002
Howl's Moving Castle, 2004
Tales from Earthsea, 2006
Ponyo, 2008
Arrietty, 2010
From Up on Poppy Hill, 2011
The Wind Rises, 2013
The Tale of the Princess Kaguya, 2013
When Marnie Was There, 2014

Agora você usou os recursos do JavaScript async/await para gerenciar código assíncrono.

Conclusão

Neste tutorial, você aprendeu como o JavaScript lida com a execução de funções e o gerenciamento de operações assíncronas com o loop de eventos. Em seguida, você escreveu programas que criaram um arquivo CSV após fazer uma solicitação HTTP para dados de filmes usando várias técnicas de programação assíncrona. Primeiro, você usou a abordagem obsoleta baseada em callbacks. Em seguida, você usou promessas e, finalmente, async/await para tornar a sintaxe de promessa mais sucinta.

Com sua compreensão do código assíncrono com Node.js, você agora pode desenvolver programas que se beneficiam da programação assíncrona, como aqueles que dependem de chamadas de API. Dê uma olhada nesta lista de APIs públicas. Para usá-las, você terá que fazer solicitações HTTP assíncronas como fizemos neste tutorial. Para estudo adicional, tente construir um aplicativo que utilize essas APIs para praticar as técnicas que você aprendeu aqui.

Source:
https://www.digitalocean.com/community/tutorials/how-to-write-asynchronous-code-in-node-js