Определение асинхронного кода: когда порядок, в котором вы читаете код, не совпадает с порядком, в котором код выполняется.
Что изменилось в мире 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, и мы добавим вас в качестве автора. Также сообщите нам, к каким публикациям вы хотите быть добавлены.