Как писать асинхронный код в Node.js

Автор выбрал Фонд открытого интернета/Свободы слова для получения пожертвования в рамках программы Написать ради Пожертвований.

Введение

Для многих программ на JavaScript код выполняется так, как его пишет разработчик — построчно. Это называется синхронное выполнение, потому что строки выполняются одна за другой, в том порядке, в котором они были написаны. Однако не каждая инструкция, которую вы даете компьютеру, должна быть выполнена сразу. Например, если вы отправляете сетевой запрос, процесс выполнения вашего кода должен будет подождать возврата данных, прежде чем сможет работать с ними. В этом случае время будет потеряно, если он не будет выполнять другой код, пока ждет завершения сетевого запроса. Чтобы решить эту проблему, разработчики используют асинхронное программирование, при котором строки кода выполняются в другом порядке, чем тот, в котором они были написаны. С помощью асинхронного программирования мы можем выполнять другой код, пока мы ждем завершения длительных операций, таких как сетевые запросы.

Код JavaScript выполняется в одном потоке в рамках процесса компьютера. Его код обрабатывается синхронно в этом потоке, с выполнением только одной инструкции за раз. Поэтому, если мы выполняем долгую задачу в этом потоке, весь оставшийся код блокируется до завершения задачи. Используя асинхронные функции JavaScript, мы можем выгрузить долгие задачи на фоновый поток, чтобы избежать этой проблемы. Когда задача завершается, код, который нам нужно выполнить для обработки данных задачи, снова помещается в основной одиночный поток.

В этом учебнике вы узнаете, как JavaScript управляет асинхронными задачами с помощью Цикла событий, который является конструкцией JavaScript, завершающей новую задачу, пока ожидается другая. Затем вы создадите программу, использующую асинхронное программирование для запроса списка фильмов из API студии Ghibli и сохранения данных в CSV файл. Асинхронный код будет написан тремя способами: обратные вызовы, промисы и с использованием ключевых слов async/await.

Примечание: На момент написания этой статьи асинхронное программирование больше не выполняется только с помощью обратных вызовов, но изучение этого устаревшего метода может дать отличный контекст для понимания того, почему сообщество JavaScript теперь использует промисы. Ключевые слова async/await позволяют нам использовать промисы менее многословно и, таким образом, являются стандартным способом выполнения асинхронного программирования в JavaScript на момент написания этой статьи.

Предварительные требования

Цикл событий

Давайте начнем с изучения внутреннего устройства выполнения функций JavaScript. Понимание того, как это ведет себя, позволит вам более целенаправленно писать асинхронный код и поможет вам в устранении неполадок в будущем.

Поскольку интерпретатор JavaScript выполняет код, каждая вызываемая функция добавляется в стек вызовов JavaScript. Стек вызовов является стеком – структурой данных, похожей на список, где элементы могут быть добавлены только сверху и удалены только сверху. Стеки следуют принципу “последним пришел, первым ушел” или LIFO. Если вы добавите два элемента в стек, самый недавно добавленный элемент будет удален первым.

Давайте проиллюстрируем это с помощью примера с использованием стека вызовов. Если JavaScript находит вызов функции functionA(), она добавляется в стек вызовов. Если эта функция functionA() вызывает другую функцию functionB(), то functionB() добавляется на вершину стека вызовов. После завершения выполнения функции JavaScript удаляет ее из стека вызовов. Следовательно, JavaScript сначала выполнит functionB(), удалит ее из стека, когда она будет завершена, а затем завершит выполнение functionA() и удалит ее из стека вызовов. Вот почему внутренние функции всегда выполняются раньше, чем внешние функции.

Когда JavaScript сталкивается с асинхронной операцией, например, записью в файл, он добавляет ее в таблицу в своей памяти. В этой таблице хранится операция, условие для ее завершения и функция, которая должна быть вызвана по завершении. По мере завершения операции JavaScript добавляет связанную функцию в очередь сообщений. Очередь – это еще одна структура данных, похожая на список, в которую элементы могут быть добавлены только внизу, но удалены сверху. В очереди сообщений, если две или более асинхронные операции готовы к выполнению своих функций, то асинхронная операция, завершившаяся первой, будет иметь свою функцию помеченную для выполнения первой.

Функции в очереди сообщений ожидают добавления в стек вызовов. Цикл событий – это постоянный процесс, который проверяет, пуст ли стек вызовов. Если да, то первый элемент в очереди сообщений перемещается в стек вызовов. JavaScript отдает приоритет функциям в очереди сообщений перед вызовами функций, которые он интерпретирует в коде. Совместное воздействие стека вызовов, очереди сообщений и цикла событий позволяет обрабатывать код JavaScript, управляя асинхронными действиями.

Теперь, когда у вас есть общее представление о цикле событий, вы знаете, как будет выполняться асинхронный код, который вы пишете. Обладая этими знаниями, вы теперь можете создавать асинхронный код с тремя различными подходами: обратные вызовы, обещания и async/await.

Асинхронное программирование с обратными вызовами

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.

Долгое время обратные вызовы были наиболее распространенным механизмом для написания асинхронного кода, но сейчас они в основном устарели, потому что они могут делать код запутанным для чтения. В этом шаге вы напишете пример асинхронного кода с использованием обратных вызовов, чтобы использовать его в качестве базового значения для оценки повышенной эффективности других стратегий.

Существует много способов использования функций обратного вызова в другой функции. Обычно они имеют следующую структуру:

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

Хотя JavaScript или Node.js не требуют синтаксически, чтобы функция обратного вызова была последним аргументом внешней функции, это общая практика, которая облегчает идентификацию обратных вызовов. Также обычно JavaScript-разработчики используют анонимные функции в качестве обратного вызова. Анонимные функции – это функции, созданные без имени. Обычно гораздо легче читать, когда функция определена в конце списка аргументов.

Чтобы продемонстрировать обратные вызовы, давайте создадим модуль Node.js, который записывает список фильмов Studio Ghibli в файл. Сначала создайте папку, в которой будет храниться наш файл JavaScript и его вывод:

  1. mkdir ghibliMovies

Затем перейдите в эту папку:

  1. cd ghibliMovies

Мы начнем с создания HTTP-запроса к API Studio Ghibli, результаты которого наша функция обратного вызова будет записывать в журнал. Для этого мы установим библиотеку, которая позволяет нам получать доступ к данным HTTP-ответа в обратном вызове.

В терминале инициализируйте npm, чтобы у нас была ссылка на наши пакеты позже:Затем установите библиотеку request:

  1. npm init -y

Затем установите библиотеку request:

  1. npm i request --save

Теперь откройте новый файл с названием callbackMovies.js в текстовом редакторе, например, nano:

  1. nano callbackMovies.js

В своем текстовом редакторе введите следующий код. Начнем с отправки HTTP-запроса с помощью модуля request:

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

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

В первой строке мы загружаем модуль request, установленный через npm. Модуль возвращает функцию, которая может делать HTTP-запросы; затем мы сохраняем эту функцию в постоянной request.

Затем мы делаем HTTP-запрос с помощью функции request(). Теперь давайте распечатаем данные из HTTP-запроса в консоль, добавив выделенные изменения:

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']}`);
    });
});

Когда мы используем функцию request(), мы даем ей два параметра:

  • URL веб-сайта, который мы пытаемся запросить
  • A callback function that handles any errors or successful responses after the request is complete

Наша функция обратного вызова имеет три аргумента: error, response и body. Когда HTTP-запрос завершен, аргументы автоматически получают значения в зависимости от исхода. Если запрос не удалось отправить, то error будет содержать объект, но response и body будут null. Если запрос был успешно выполнен, то HTTP-ответ хранится в response. Если наш HTTP-ответ возвращает данные (в данном примере мы получаем JSON), то данные устанавливаются в body.

Наша функция обратного вызова сначала проверяет, получили ли мы ошибку. Лучшая практика – сначала проверять наличие ошибок в обратном вызове, чтобы выполнение обратного вызова не продолжалось с отсутствующими данными. В этом случае мы регистрируем ошибку и выполнение функции. Затем мы проверяем код состояния ответа. Наш сервер может быть недоступен, и API могут измениться, что приведет к тому, что ранее осмысленные запросы станут некорректными. Проверяя, что код состояния равен 200, что означает, что запрос был “OK”, мы можем быть уверены, что наш ответ соответствует нашим ожиданиям.

Наконец, мы разбираем тело ответа в Array и проходимся по каждому фильму, чтобы зарегистрировать его название и год выпуска.

После сохранения и выхода из файла запустите этот скрипт с помощью:

  1. node callbackMovies.js

Вы получите следующий вывод:

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

Мы успешно получили список фильмов Studio Ghibli с годом их выпуска. Теперь давайте завершим эту программу, записав список фильмов, который мы в настоящее время регистрируем, в файл.

Обновите файл callbackMovies.js в вашем текстовом редакторе, чтобы добавить следующий выделенный код, который создает файл CSV с нашими данными о фильмах:

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

Обратите внимание на выделенные изменения, мы импортируем модуль fs. Этот модуль стандартен во всех установках Node.js и содержит метод writeFile(), который может асинхронно записывать данные в файл.

Вместо записи данных в консоль мы теперь добавляем их в строковую переменную movieList. Затем мы используем writeFile(), чтобы сохранить содержимое movieList в новый файл — callbackMovies.csv. Наконец, мы предоставляем обратный вызов функции writeFile(), который имеет один аргумент: error. Это позволяет нам обрабатывать случаи, когда мы не можем записать в файл, например, когда пользователь, на котором мы выполняем процесс node, не имеет соответствующих разрешений.

Сохраните файл и запустите эту программу Node.js еще раз с помощью:

  1. node callbackMovies.js

В вашей папке ghibliMovies появится файл callbackMovies.csv со следующим содержимым:

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

Важно отметить, что мы записываем в наш CSV-файл в обратном вызове HTTP-запроса. Когда код находится в функции обратного вызова, он будет записывать данные в файл только после завершения HTTP-запроса. Если бы мы хотели связаться с базой данных после записи нашего CSV-файла, мы бы создали еще одну асинхронную функцию, которая бы вызывалась в обратном вызове writeFile(). Чем больше асинхронного кода у нас есть, тем больше обратных вызовов необходимо вложить.

Допустим, мы хотим выполнить пять асинхронных операций, каждая из которых может быть выполнена только после завершения предыдущей. Если бы мы это написали, то получилось бы что-то вроде этого:

doSomething1(() => {
    doSomething2(() => {
        doSomething3(() => {
            doSomething4(() => {
                doSomething5(() => {
                    // конечное действие
                });
            });
        }); 
    });
});

Использование Промисов для лаконичного асинхронного программирования

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.

Промисы, как правило, имеют следующий вид:

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

Как показано в этом шаблоне, промисы также используют функции обратного вызова. У нас есть функция обратного вызова для метода then(), которая выполняется, когда промис выполнен. У нас также есть функция обратного вызова для метода catch(), чтобы обрабатывать любые ошибки, которые могут возникнуть во время выполнения промиса.

Давайте получим первоочередный опыт работы с промисами, переписав наш программу Studio Ghibli для использования промисов.

Axios – это клиент HTTP с промисами на JavaScript, поэтому давайте установим его:

  1. npm i axios --save

Теперь, с помощью вашего редактора текста по выбору, создайте новый файл promiseMovies.js:

  1. nano promiseMovies.js

Наша программа будет делать HTTP-запрос с помощью axios и затем использовать специальную промис-основанную версию fs, чтобы сохранить в новый файл CSV.

Введите этот код в promiseMovies.js, чтобы мы могли загрузить Axios и отправить HTTP-запрос к API фильмов:

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

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

На первой строке мы загружаем модуль axios, сохраняя возвращенную функцию в константу с именем axios. Затем мы используем метод axios.get() для отправки HTTP-запроса к API.

Метод axios.get() возвращает обещание. Давайте свяжем это обещание, чтобы мы могли вывести список фильмов Ghibli в консоль:

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']}`);
        });
    })

Разберем, что происходит. После выполнения HTTP GET-запроса с помощью axios.get(), мы используем функцию then(), которая выполняется только тогда, когда обещание выполнено. В этом случае мы выводим фильмы на экран, как это было в примере с обратными вызовами.

Для улучшения этой программы добавьте выделенный код для записи HTTP-данных в файл:

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

Дополнительно мы импортируем модуль fs еще раз. Обратите внимание, что после импорта fs у нас есть .promises. Node.js включает в себя версию библиотеки fs на основе обещаний, поэтому обратная совместимость не нарушается в старых проектах.

Первая функция then(), которая обрабатывает HTTP-запрос, теперь вызывает fs.writeFile() вместо вывода на консоль. Поскольку мы импортировали версию fs на основе обещаний, наша функция writeFile() также возвращает обещание. Поэтому мы добавляем еще одну функцию then() для случая, когда обещание writeFile() выполнено.

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.

Примечание: В этом примере мы не проверяли код состояния HTTP, как это было сделано в примере с обратным вызовом. По умолчанию axios не выполняет свое обещание, если получает код состояния, указывающий на ошибку. Поэтому нам больше не нужно его проверять.

Чтобы завершить эту программу, прикрепите обещание к функции catch(), как показано ниже:

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

Если какое-либо обещание не будет выполнено в цепочке обещаний, JavaScript автоматически переходит к функции catch(), если она была определена. Поэтому у нас есть только один клоз catch(), даже если у нас две асинхронные операции.

Давайте подтвердим, что наша программа производит тот же вывод, запустив:

  1. node promiseMovies.js

В вашей папке ghibliMovies вы увидите файл promiseMovies.csv, содержащий:

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

С помощью обещаний мы можем написать гораздо более краткий код, чем при использовании только обратных вызовов. Цепочка обещаний обратных вызовов является более чистым вариантом, чем вложенные обратные вызовы. Однако, по мере того как мы делаем больше асинхронных вызовов, наша цепочка обещаний становится длиннее и сложнее в поддержке.

Громоздкость обратных вызовов и обещаний проистекает из необходимости создания функций, когда у нас есть результат асинхронной задачи. Лучшим вариантом было бы дождаться асинхронного результата и поместить его в переменную вне функции. Таким образом, мы можем использовать результаты в переменных, не создавая функцию. Мы можем добиться этого с помощью ключевых слов async и await.

Написание JavaScript с использованием async/await

Ключевые слова async/await предоставляют альтернативный синтаксис при работе с обещаниями. Вместо того чтобы иметь результат обещания доступным в методе then(), результат возвращается как значение, как в любой другой функции. Мы определяем функцию с ключевым словом async, чтобы сообщить JavaScript, что это асинхронная функция, возвращающая обещание. Мы используем ключевое слово await, чтобы сообщить JavaScript вернуть результаты обещания, а не само обещание, когда оно выполняется.

В общем, использование async/await выглядит следующим образом:

async function() {
    await [Asynchronous Action]
}

Посмотрим, как использование async/await может улучшить нашу программу Studio Ghibli. Используйте текстовый редактор для создания и открытия нового файла asyncAwaitMovies.js:

  1. nano asyncAwaitMovies.js

В вашем только что открытом файле JavaScript начнем с импорта тех же модулей, которые мы использовали в нашем примере с обещанием:

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

Импорты такие же, как в promiseMovies.js, потому что async/await использует обещания.

Теперь мы используем ключевое слово async, чтобы создать функцию с нашим асинхронным кодом:

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

async function saveMovies() {}

Мы создаем новую функцию с именем saveMovies(), но включаем async в начале её определения. Это важно, так как мы можем использовать ключевое слово await только в асинхронной функции.

Используйте ключевое слово await, чтобы выполнить HTTP-запрос, который получает список фильмов из API 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`;
    });
}

В нашей функции saveMovies() мы делаем HTTP-запрос с помощью axios.get(), как и раньше. На этот раз мы не привязываем его к функции then(). Вместо этого мы добавляем await перед вызовом. Когда JavaScript встречает await, он выполнит оставшуюся часть кода функции только после того, как axios.get() завершит выполнение и установит переменную response. Остальной код сохраняет данные о фильмах, чтобы мы могли записать их в файл.

Давайте запишем данные о фильмах в файл:

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

Мы также используем ключевое слово await, когда записываем данные в файл с помощью fs.writeFile().

Чтобы завершить эту функцию, нам нужно перехватывать ошибки, которые могут возникнуть. Давайте сделаем это, инкапсулируя наш код в блок 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}`);
    }
}

Так как обещания могут завершиться неудачей, мы оборачиваем наш асинхронный код в блок try/catch. Это позволит захватить любые ошибки, которые возникают, когда либо запрос HTTP, либо операции записи в файл завершаются с ошибкой.

Наконец, давайте вызовем нашу асинхронную функцию saveMovies(), чтобы она выполнилась при запуске программы с помощью 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();

На первый взгляд это выглядит как типичный блок кода на синхронном JavaScript. В нем меньше функций, которые передаются, что выглядит более аккуратно. Эти небольшие настройки делают асинхронный код с использованием async/await более удобным для поддержки.

Протестируйте эту итерацию нашей программы, введя это в вашем терминале:

  1. node asyncAwaitMovies.js

В вашей папке ghibliMovies будет создан новый файл asyncAwaitMovies.csv со следующим содержимым:

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

Вы теперь использовали возможности JavaScript async/await для управления асинхронным кодом.

Заключение

В этом руководстве вы узнали, как JavaScript обрабатывает выполнение функций и управление асинхронными операциями с помощью цикла событий. Затем вы написали программы, которые создавали файл CSV после выполнения HTTP-запроса для получения данных о фильмах, используя различные асинхронные техники программирования. Сначала вы использовали устаревший подход на основе обратного вызова. Затем вы использовали промисы, и, наконец, async/await, чтобы сделать синтаксис промисов более лаконичным.

С помощью вашего понимания асинхронного кода с Node.js вы теперь можете разрабатывать программы, которые получают выгоду от асинхронного программирования, например, те, которые зависят от вызовов API. Взгляните на этот список публичных API. Чтобы использовать их, вам придется делать асинхронные HTTP-запросы, подобные тем, которые мы делали в этом уроке. Для дальнейшего изучения попробуйте создать приложение, которое использует эти API, чтобы практиковать техники, которые вы изучили здесь.

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