img

Асинхронные функции JavaScript: что это такое и как ими пользоваться

21 ноября
20:00
Бесплатный вебинар
Введение в Docker
Ведущий — Филипп Игнатенко.
Руководитель центра разработки
Записаться
img
img

 

Если вы когда-либо писали приложения на JavaScript, то, скорее всего, сталкивались с асинхронными функциями, например, функцией fetch в браузере и функцией readFile в Node.js.

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

1. Введение в синхронные функции

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

За выполнение большей части функций отвечает процессор, а это значит, что во время выполнения ранее упомянутых функций (не важно, сколько времени это займет) процессор будет полностью занят. Это так называемые синхронные функции. Ниже приведен пример такой функции:

function add(a, b) {
    for (let i = 0; i < 1000000; i ++) {
        // Do nothing { // Ничего не происходит }
    }
    return a + b;
}

// Calling the function will take a long time { // Вызов функции займет много времени }
sum = add(10, 5);

// However, the processor cannot move to the { // При этом процессор не может перейти к следующему действию }
console.log(sum);

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

После того, как мы определили функцию, мы ее вызвали и сохранили результат в переменной sum. Даже при том, что выполнение функции add занимает немало времени, процессор не может перейти к выводу суммы, пока не завершится ее выполнение.  

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

2. Введение в асинхронные функции

Асинхронные функции выполняют большую часть своей работы за пределами процессора. Это значит, что даже если выполнение функции занимает довольно много времени, процессор остается свободным и может выполнить какую-то другую задачу. 

Ниже приведен пример такой функции:

fetch('https://jsonplaceholder.typicode.com/users/1');

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

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

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

3. Что такое промисы в JavaScript?

В JavaScript промис – это временное значение, которые возвращается асинхронной функцией. Промисы – это основа современного асинхронного программирования на JavaScript.

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

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

Ниже приведен пример, в котором считываются данные из файла в Node.js:

const fs = require('fs/promises');

fileReadPromise = fs.readFile('./hello.txt', 'utf-8');

fileReadPromise.then((data) => console.log(data));

fileReadPromise.catch((error) => console.log(error));

В первой строке мы импортируем модуль fs/promises.

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

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

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

Существует также альтернативный подход – ключевые слова async и await. Давайте рассмотрим его. 

4. Что такое async и await?

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

Ключевое слово await используется для приостановки выполнения функции в процессе ожидания завершения асинхронной функции. Ниже приведен пример:

const fs = require('fs/promises');

function readData() {
const data = await fs.readFile('./hello.txt', 'utf-8');

    // This line will not be executed until the data becomes available { // эта строка не будет выполнена до тех пор, пока мы не получим данные }
console.log(data);
}

readData()

Мы использовали ключевое слово await при вызове функции readFile. Таким образом, мы указали процессору, что, прежде чем он сможет выполнить следующую строчку (console.log), ему необходимо дождаться, пока файл будет прочитан. Это гарантирует, что код, который зависит от результата асинхронной функции, не будет выполняться до тех пор, пока мы не получим этот самый результат.

Если вы попробуете запустить код, приведенный выше, то получите ошибку. Это потому, что ключевое слово await можно использовать только внутри асинхронной функции. Для того, чтобы объявить функцию как асинхронную, вам необходимо указать ключевое слово async перед объявлением функции:

const fs = require('fs/promises');

async function readData() {
const data = await fs.readFile('./hello.txt', 'utf-8');

    // This line will not be executed until the data becomes available { // Эта строка не будет выполнена до тех пор, пока мы не получим данные }
console.log(data);
}

// Calling the function so it runs { // Вызов функции; функция запускается }
readData()

// Code at this point will run while waiting for the readData function to complete { // На данном этапе код будет выполняться только после завершения выполнения функции readData }
console.log('Waiting for the data to complete')

Запустив этот фрагмент кода, вы увидите, что JavaScript выполняет внешний console.log только после того, как дождется данных, которые должны быть прочитаны из текстового файла. После того, как последнее выполнится, запуститься console.log внутри readData.

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

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

5. Введение в обратные вызовы

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

Ниже приведен пример чтения файла в Node.js:

const fs = require("fs");

fs.readFile("./hello.txt", "utf-8", (err, data) => {

// In this callback, we put all code that requires { // В эту функцию обратного вызова мы помещаем весь необходимый код }
if (err) console.log(err);
else console.log(data);
});

// In this part here we can perform all the tasks that do not require the result { // В этой части программы мы можем выполнять все задачи, которые не требуют получения результата }
console.log("Hello from the program")

В первой строке мы импортируем модуль fs. Далее мы вызываем функцию readFile, которая находится в этом модуле. Эта функция считает текст из указанного нами файла. Первый аргумент – это файл, который нужно прочитать, а второй – формат файла. 

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

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

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

Если мы запустим приведенный выше код, то получим следующий результат:

6. Ключевые особенности JavaScript

Некоторые ключевые особенности и характеристики JavaScript влияют на работу асинхронного кода. Более подробно вы можете о них узнать их следующего видео:

[видео]

Ниже я кратко изложил две важные особенности. 

#1. Однопоточность

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

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

#2. Управление по событиям

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

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

7. Что необходимо учитывать при написании асинхронного JavaScript

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

Поддержка браузера

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

А эта таблица демонстрирует то, как различные браузеры поддерживают асинхронные функции.

Практические рекомендации

  • Всегда делайте выбор в пользу async/await. Это поможет вам писать более чистый код, который будет легко понимать.
  • Выполняйте обработку ошибок в блоках try/catch.

Используйте ключевое слово async только в том случае, если вам необходимо дождаться результата выполнения функции.

Степень важности асинхронного кода

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

Заключение

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

К тому же мы рассмотрели ключевые особенности JavaScript. И мы завершили статью, рассказав про поддержку браузеров и предоставив некоторые рекомендации.

Ссылка
скопирована
Получите бесплатные уроки на наших курсах
Все курсы
Программирование
Скидка 25%
Фронтенд-разработчик с нуля
Погрузитесь в мир веб-разработки, освоив основные инструменты работы: HTML, CSS, JavaScript. Научитесь работать с дизайн-макетами и адаптивной версткой, сверстаете свои первые страницы и поймете, как строить карьерный трек в ИТ.
Получи бесплатный
вводный урок!
Пожалуйста, укажите корректный e-mail
отправили вводный урок на твой e-mail!
Получи все материалы в telegram и ускорь обучение!
img
Еще по теме:
img
Гипервизор - это программное обеспечение для виртуализации, используемое для создания и запуска виртуальных машин (ВМ). Гипервиз
img
Виртуализация серверов позволяет запускать несколько виртуальных машин на одном физическом сервере. Запуск виртуальных машин (ВМ
img
Сегодня мы рассмотрим, как настроить и использовать PHP в проекте. Но прежде чем начать, нужно понять, что такое PHP. Что такое
img
Как разработчик, вы знаете, что HTML расшифровывается как HyperText Markup Language (язык разметки гипертекста). HTML — это язык
img
Бесконечные споры вокруг искусственного интеллекта приводят к путанице. Существует много терминов, которые кажутся похожими, но
img
SVG расшифровывается как масштабируемая векторная графика. Это веб-дружелюбный векторный формат файлов, используемый для отображ
21 ноября
20:00
Бесплатный вебинар
Введение в Docker