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

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

JavaScript синхронен по своей природе. Но есть несколько вещей, которые ведут себя асинхронно, например вызовы API, setTimeout, события DOM (щелчок, наведение курсора, прокрутка…). Представьте, что вы делаете запрос к дорогостоящему вызову базы данных. JavaScript является однопоточным, поэтому он может выполнять одно действие за раз. Следовательно, если бы это было синхронно, вы не смогли бы ничего сделать, пока не придет ответ от вызова базы данных.

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

Чтобы понять, как ведет себя асинхронный код, давайте рассмотрим пример.

Что вы думаете о выводе приведенного выше фрагмента кода? Это как ниже.

Start
End
Inside timeout

Да. Таков порядок. Не начало, внутренний тайм-аут и конец.

Что делать, если задержка установлена ​​​​на 0 миллисекунд. Будет ли он работать в правильном порядке? Не совсем. Выход будет таким же. Итак, что здесь происходит? Давайте зайдем за занавеску и посмотрим, что происходит.

  • Сначала код выполнит console.log(‘Start’). Как только это будет сделано, выскочит из стека.
  • Следующая строка setTimeout. Как упоминалось ранее, он демонстрирует асинхронное поведение. Поэтому он будет перенесен на веб-API (можно сказать, на C++ API для NodeJS).
  • Затем JavaScript выполнит console.log('End'). После завершения он также удаляется из стека.
  • Задержка setTimeot составляет 1 секунду. Таким образом, через 1 секунду оно будет помещено в очередь задач.
  • Здесь вступает в действие цикл событий. Работа цикла событий заключается в наблюдении за стеком и очередью задач. Как только стек станет пустым, он возьмет первый из очереди и поместит его в стек

Теперь вы знаете, почему он не производит никакого изменения порядка, даже если мы делаем нулевую задержку для setTimeout!

До сих пор асинхронное поведение звучит неплохо. Почему мы должны справиться с этим?

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

Давайте посмотрим пример.

Здесь мы собираемся найти yдля данногоx,следующего уравнения.

                          y = 2x + 1

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

Что вы думаете о выходе?

Он покажет «неопределенное». Как вы знаете, причина в том, что console.log(result) не ждет, пока multiplyBy2()и add1() функции возвращают значения. Итак, как мы смягчим проблему? Здесь у нас есть 3 подхода.

  1. Функции обратного вызова
  2. Обещания
  3. Асинхронное ожидание

Давайте посмотрим, как мы можем их использовать.

Обратные вызовы

Здесь мы передаем функцию (называемую функцией обратного вызова) в качестве аргумента обеим функциям: multiplyBy2() и add1(). После возврата вывода у нас есть результат в теле функции обратного вызова.

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

Обещания

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

new Promise((resolve, reject) => {
  if (err) reject()
  resolve()
}).then(() => {
  // Will execute once task is resolved (resolve)
}).catch(() => {
  // will exectue if an error occurred (reject)
})

Объект Promise принимает функцию (она называется функцией-исполнителем) в качестве аргумента. Как только задача выполнена, она переходит в блок. В случае ошибки он переходит в блок catch.

Как видите, этот способ написания кода намного чище, чем использование обратных вызовов. Мы можем связать n блоков then по мере необходимости.

Асинхронное ожидание

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

Вы можете найти рабочий пример здесь.

https://github.com/Kanchana46/callback-promises-async-await.git

Это все о работе с асинхронной природой. Удачного кодирования!.