Если вы стремитесь стать отличным разработчиком JavaScript, вам необходимо хорошо разбираться в асинхронном Javascript.

Что такое асинхронность?

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

Вы не добавляете ингредиенты, пока не добавите воду. Они происходят по порядку; операции выполняются синхронно.

Асинхронный: «добавьте ингредиенты и подождите, пока они закипят. А пока позволь мне испечь печенье к чаю ». Не нужно ждать, пока чай закипит, прежде чем начинать печь.

Когда и где мы видим или используем синхронный материал?

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

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

Второй - действия пользователей (как видно, это любые сайты). Например: у меня есть особая функция (например, изменение цвета выделения темы моей веб-страницы), которая запускается, когда пользователь нажимает кнопку. Поскольку я не знаю, когда именно пользователь нажмет кнопку, это обязательно асинхронно. Итак, мне нужно создать функцию, которая запускается «всякий раз, когда пользователь щелкает, меняет цвет».

Кроме того, такие действия (например, щелчок, прокрутка) никогда не должны блокироваться. У меня может быть функция при событии щелчка, которая анализирует видео на моей веб-странице. Это может занять 10 секунд или 1 час. Я действительно не могу попросить пользователя подождать неопределенное время для завершения преобразования, прежде чем ему нужно будет прокрутить или щелкнуть что-нибудь еще. Что еще хуже, эта функция может ожидать ответа через 5 секунд для работы над чем-то другим (например, преобразованием видео в аудиоформат). В любом случае, если у меня нет функции, которая вызывается (преобразование в аудио) всякий раз, когда функция получает ответ (видеоответ), я пропущу этот ответ.

Что такое JavaScript?

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

Итак, как же нам тогда выполнить всю ту асинхронную работу, о которой мы говорили выше?

Ответ - асинхронная среда, предоставляемая браузерами, на которых запущен JS.
Сам JavaScript является синхронным языком. Однако в веб-браузерах есть движки JS, которые выполняют код JS и состоят из кучи (материал для выделения памяти) и стека вызовов для выполнения. Они также предоставляют другие webAPI, такие как DOM, AJAX и XMLHTTPRequests, setTimeOut и т. Д. Наиболее распространенным из них является движок V8 в Chrome, который также используется в Node.js и Deno (так что это не ограничивается только браузерами). Весь код блокировки переходит к веб-интерфейсу API, а затем к циклу обработки событий.

Цикл событий в JS

Поскольку JS имеет только один главный стек вызовов (однопоточный и может выполнять только одно действие за раз), асинхронные обратные вызовы являются ответом на проблему с блокировкой запросов.

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

Это видео Филипа Робертса - лучшее объяснение цикла событий, которое я когда-либо встречал:
https://www.youtube.com/watch?v=8aGhZQkoFbQ&ab_channel=JSConf

Итак, в основном:

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

If blocking code {
   run this blocking code with help of webAPIs
   send this blocking code to callback/task queue
   let event loop check the availability in call stack
   if call stack is ready to take the blocking task {
      send it to main call stack
   }
   else {
      wait until ready
   }
}

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

Обратные вызовы, обещания и асинхронное ожидание

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

Помните приведенный выше пример процесса преобразования видео на веб-странице? У нас была функция (вызовем convertToAudio ()), которая вызывается, когда возвращается ответ от другой функции (давайте вызовем parseVideo ()).

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

let parseVideo = function(path, callback){
  let resp = doParsing(path)
    if(resp) // response is not undefined
      callback(resp) // success << this callback is convertToAudio()
    else
      return("error")
    })
}
function convertToAudio(videoResponse){
  makeAudioFromData(videoResponse) 
}
// call parse function
parseVideo("path/of/video", convertToAudio)

Большинство методов массива (например, filter, map, find и т. Д.) также используют обратные вызовы:

[1,2,3,4].map(function(i){ // this function is also callback
  return i*2
})
// These are synchronous callbacks unlike above video example which shows asynchronous callbacks

Вы должны знать, что обратные вызовы - это просто функции, которые могут быть переданы в качестве аргумента для использования в другой функции. Они могут быть а / синхронными. В браузерах: setTimeout, setInterval, запросы и события асинхронны. Другие встроенные обратные вызовы (например, Array.prototype.map) синхронны.

Давайте рассмотрим пример, чтобы понять:

const syncFunc = (data) => {
  // some normal calculations with if or loop which are synchronous
  if (data === "sync") console.log("true")
  else console.log("false")
}
const asyncFunc = (data) => {
  setTimeout(function(){
    console.log(`${data} will be shown after 3 sec!`)
  }, 3000)
}
const mainFunc = (type, callback) => {
  console.log(`Do you get why this calls the ${type} callback?`)
  callback(type)
}

mainFunc("async", asyncFunc)
mainFunc("sync", syncFunc)
// Result
Do you get why this calls the async callback?
Do you get why this calls the sync callback?
true
async will be shown after 3 sec!

Обещать

Объект Promise представляет возможное завершение (или сбой) асинхронной операции и ее результирующее значение.

Он имеет 3 состояния: ожидает выполнения, заполнено, отклонено.

Для приведенного выше примера: если мы заставим функцию parseVideo возвращать обещание, тогда:

function parseVideo(path) {
  return new Promise(function(resolve,reject) {
    let resp = doParsing(path)
    if(resp) // response is not undefined
      resolve(resp) // success
    else
      reject("error")
    })
}
function convertToAudio(videoResponse){
  makeAudioFromData(videoResponse)
}
// calling the function
parseVideo("path/of/video")
.then(videoResponse => convertToAudio(videoResponse))
.catch(error => console.log("error on parsing video", error))

функция parseVideo () при первом вызове будет в состоянии ожидания. Если parseVideo работает успешно, обещание выполнено, и мы получаем ответ в блоке then. Если он выдает ошибку, мы перехватываем ошибку отклонено в блоке catch.

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

Async / Await - это языковая функция, которая является частью стандарта ES8. Они построены на обещаниях.

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

// we use them to call the promisified function
function parseVideo(path) {
  return new Promise(function(resolve,reject) {
    let resp = doParsing(path)
    if(resp) // response is not undefined
      resolve(resp) // success
    else
      reject("error")
    })
}
function convertToAudio(videoResponse){
  makeAudioFromData(videoResponse)
}
// await is always called in async function
async function getVid(){
  let videoResponse = await parseVideo("path/to/video")
  convertToAudio(videoResponse)
}

Async / await предназначен для блокировки кода. Используйте Promise.all () для параллельного выполнения множества асинхронных вызовов.