Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Node.js
Code

JavaScript-обратные вызовы, обещания и асинхронные функции: часть 1

by
Difficulty:AdvancedLength:MediumLanguages:
This post is part of a series called JavaScript Callbacks, Promises and Async Functions.
JavaScript Callbacks, Promises, and Async Functions: Part 2

Russian (Pусский) translation by Masha Kolesnikova (you can also view the original English article)

Введение

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

Задачи, которые могут блокировать наше приложение, включают в себя создание HTTP-запросов, запрос к базе данных или открытие файла. Некоторые языки, такие как Java, справляются с этим, создавая несколько потоков. Однако JavaScript имеет только один поток, поэтому нам нужно писать наши программы таким образом, чтобы никакая задача не блокировала поток.

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

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

Содержание

  • Потоки
  • Синхронный и асинхронный
  • Функции обратного вызова
  • Резюме
  • Ресурсы

Потоки

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

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

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

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

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

Это похоже на то, как работает асинхронное программирование. Кассир смог дождаться меня. Иногда они это делают. Но лучше не тратить время и обрабатывать других клиентов. Дело в том, что клиенты не должны обрабатываться в том порядке, в котором они находятся в очереди. Аналогично, код не должен выполняться в том порядке, в котором мы его пишем.

Синхронное и асинхронное

Естественно думать, что наш код выполняется последовательно сверху донизу. Это синхронное выполнение. Однако с JavaScript некоторые задачи по сути являются асинхронными (например, setTimeout), а некоторые задачи, которые мы пишем, являются асинхронными, потому что мы знаем заранее, что они могут блокировать.

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

Это синхронно:

index.js

db/posts.json

db/comments.json

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

У node есть метод readFile, который мы можем использовать, чтобы открыть файл асинхронно. Вот его синтаксис:

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

Чтобы проиллюстрировать проблему, давайте рассмотрим более простой пример. Как вы думаете, будет ли напечатан следующий код?

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

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

Функции обратного вызова

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

Обратные вызовы позволяют нам принудительно выполнять задачи. Они также помогают нам, когда у нас есть задачи, зависящие от результатов предыдущей задачи. Используя обратные вызовы, мы можем исправить наш последний пример, чтобы он печатал «первый», «второй», а затем «третий».

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

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

И это наш код инициализации будет выглядеть с использованием обратных вызовов:

Одно замечание в нашей функции loadCollection заключается в том, что вместо использования инструкции try/catch для обработки ошибок мы используем оператор if/else. Блок catch не сможет поймать ошибки, возвращенные из обратного вызова readFile.

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

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

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

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

Задача

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

Резюме

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

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

Ресурсы

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.