Определение асинхронного кода: когда порядок, в котором вы читаете код, не совпадает с порядком, в котором код выполняется.

Что изменилось в мире ES6 по сравнению с асинхронной моделью, которую я объяснил здесь, которую я рекомендую прочитать перед прочтением этого поста?

В ES6 появился Promises.

Предпосылка для Promises с учетом следующего кода

function printHello(){ console.log("Hello") }
function blockFor1Sec(){
    // Blocks the JavasCript thread for 1 second
    // Imagine a for loop that goes to a million
    for(i=0; i < 15000; i++){
        console.log("i is: ", i)
    }
}
​
setTimeout(printHello, 0);
blockFor1Sec();
console.log("Me First")

это:

Вызов setTimeout только имеет последствия в браузере. Поскольку это функция фасад.

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

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

А при разработке масштабируемых приложений это не совсем идеально.

Таким образом, наиболее ценной особенностью Promises является желание / способность сказать, когда вы запускаете что-то в фоновом режиме (фон является браузером), не просто выбрасывайте это, но пусть это будет своего рода последствия и в памяти JavaScript.

В ES6 мы представили новую «двустороннюю» функцию фасада fetch. «Двусторонний» означает, что он выполняет две вещи.

  • Запустите сетевой запрос из браузера.
  • В Javascript Land он возвращает особый вид объекта, называемый Promise Object.

Когда фоновая работа будет завершена, он заполнит и обновит этот объект обещания данными из сетевого запроса.

Этот объект обещания позволит нам отслеживать в Javascript, в нашей локальной или глобальной памяти, состояние вещей, которые мы запускали в нашем веб-браузере.

Давайте посмотрим на пример и поговорим о том, что происходит:

function display(data){
    console.log(data)
}
const futureData = fetch('https://twitter.com/will/tweets/1')
futureData.then(display);
​
console.log("Me first !");
  • строка 1: определение функции display
  • строка 4: запустите выборку и сохраните объект обещания в const futureData

fetch немедленно возвращает объект обещания с двумя свойствами.

{ value: nil, onFulfilled: [] }

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

Давайте подробнее рассмотрим, что происходит, когда мы запускаем fetch

Сетевой запрос имеет логическое значение завершено?.

  • 0 мс: завершено? это false
  • 0 мс: мы отправляем запрос в твиттер, мы знаем это, потому что передаем URL: « https://twitter.com/example/tweets/1 '. У которого есть доменное имя: twitter.com, и мы говорим ему получить первый твит "example", задав маршрут example/tweets/1

Чтобы связаться с API Twitter, требуется время, поэтому мы не знаем, когда вернется ответ.

  • тик
  • ток
  • тик
  • ток
  • Когда Twitter отправит обратно объект ответа, завершить? Преобразуется в true и обновляет свойство value объекта Promise объектом ответа из Twitter.
  • 270 мс: мы получаем данные из Twitter и сохраняем их в futureData, что запускает нашу функцию display.

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

Здесь вступает в игру свойство onFulfilled объекта Promise.

Свойство onFulfilled отвечает за удержание функциональности для запуска при возврате данных. Любая функция в этом массиве будет запущена для запуска при обновлении свойства value. Не только это, но и возвращаемое значение из Twitter будет передано в качестве аргумента для заполнения параметра любой функции, хранящейся в массиве onFulfilled.

И поэтому нам нужно, чтобы наша функция display находилась в этом массиве. Но мы не можем использовать наши обычные инструменты для добавления элементов в массив, например push, потому что свойство onFulfilled является свойством hidden в Promise Objects.

Мы используем для этого .then.

Наш обратный вызов, переданный в .then, не запускается просто, когда свойство value в нашем объекте обещания заполняется данными ответа из нашего сетевого запроса.

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

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

function display(data){ console.log(data) }
function printHello(){ console.log ("Hello!") }
function blockFor300ms(){ 
    for(i=0; i < 25000 ; i++){
        console.log("i is: ", i)
    }
}
setTimeout(printHello, 0);
​
const futureData = fetch("https://twitter.com/example/tweets/1")
futureData.then(display)
​
blockFor300ms()
console.log("Me first !")
  • 0 мс: вызывается setTimeout, он передает браузеру функцию обратного вызова, здесь printHello, и запускает таймер, по истечении которого отправляет printHello в очередь обратного вызова. Поскольку продолжительность таймера, переданная setTimeout, здесь равна 0 мс, printHello немедленно помещается в очередь обратного вызова, но еще не выполняется, потому что, как мы знаем, весь синхронный код должен быть выполнен до того, как цикл обработки событий JavaScript отправит код из очередь обратного вызова в стек вызовов.
  • 1 мс: наша Двухзубчатая фасадная функция fetch хранит объект обещания в const futureData И запускает XHR для API Twitter.
  • 2 мс: обратный вызов, здесь display добавляется к value ключу нашего futureData Promise Object.
  • 3 мс: вызывается blockFor300ms, в результате чего поток Javascript блокируется на 300 мс.
  • Тик
  • Ток
  • Тик
  • Ток
  • 270 мс: наш сетевой запрос вернулся с данными, которые немедленно сохраняются в value ключе нашего const futureData объекта обещания.
  • 300 мс: наша blockFor300ms функция завершила блокировку потока Javascript.
  • 301 мс: Выполняем console.log("Me first !").

Возникает вопрос, что выполняется в первую очередь? Наш обратный вызов передан в setTimeout или обратный вызов передан в ключ .value нашего futureData объекта обещания?

На очереди появляется Очередь микро-задач, которая представляет собой очередь для обратных вызовов, обнаруженных в массиве .onFulfilled объектов обещания.

Таким образом, цикл обработки событий, который отвечает за добавление обратных вызовов в стек вызовов из очереди обратных вызовов, также отвечает за добавление обратных вызовов, обнаруженных в очереди микрозадач.

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

.

.

.

Цикл событий сначала проверяет очередь микрозадач, а затем очередь C allback, и, таким образом, выполняет обратные вызовы в очереди задач M icro. всегда будет выполняться перед обратными вызовами в Очереди обратных вызовов.

Вернемся к нашему примеру:

  • 302 мс: Выполняем display
  • 303 мс: Выполняем printHello

Блог вдохновлен Frontend Masters / Frontend Masters, JavaScript: The Hard Parts, v2, преподается Will Sentance, на основе которого написан этот пост. Замечательный ресурс для тех, кто хочет резко расширить свои знания во внешнем программировании.

Спасибо за прочтение!

Любовь, Свет, Код ❤️

Примечание из JavaScript In Plain English

Мы запустили три новых издания! Проявите любовь к нашим новым публикациям, подписавшись на них: AI на простом английском, UX на простом английском, Python на простом английском - спасибо и продолжайте учиться!

Мы также всегда заинтересованы в продвижении качественного контента. Если у вас есть статья, которую вы хотели бы отправить в какую-либо из наших публикаций, отправьте нам электронное письмо по адресу [email protected] с вашим именем пользователя Medium, и мы добавим вас в качестве автора. Также сообщите нам, к каким публикациям вы хотите быть добавлены.