Введение

Асинхронность - один из наиболее часто используемых и наиболее важных аспектов JavaScript, поэтому давайте рассмотрим его. В этом посте мы начнем с изучения «v8» и «цикла событий». Затем мы немного поговорим о функциях обратного вызова, прежде чем изучать, что такое обещания и наблюдаемые. Идея этого поста состоит не в том, чтобы глубоко погрузиться во все эти концепции, а в том, чтобы получить четкий их обзор, чтобы увидеть, что использовать, когда использовать и как это работает.

v8 и цикл событий

«V8» - это не только овощной сок, но и название, данное движку, разработанному «Google» для запуска «JavaScript»… синхронно…

Что подождать? Я читаю сообщение об асинхронности, а вы говорите мне, что JavaScript на самом деле синхронный ?!

Не совсем так. Фактически, пока «v8» выполняет код синхронно, браузеры добавляют некоторые функции, позволяющие запускать асинхронный код:

  • setTimeout / setInterval может выполнить некоторый код через заданный промежуток времени.
  • xmlHttpRequest может отправлять запросы AJAX.
  • События DOM могут запускать код при наступлении определенного события.

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

Хорошо, я понимаю, что такое «v8», но каков цикл событий?

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

Когда в строке 11 вызывается «foo», движку нетрудно знать, что делать. Действительно, он просто переходит к функции «foo» и выполняет ее. То же самое относится к вызову «bar» в «foo». Однако после выполнения «бара» как движок узнает, где продолжить выполнение? Для нас это довольно очевидно ...

«bar» был вызван «foo», поэтому выполнение просто переходит на строку 4 и продолжается.

Вы действительно знаете это, потому что у вас есть мозг. В самом деле, если бы у вас не было мозга, а значит, и памяти, вы не смогли бы вспомнить, что «bar» был вызван «foo», поэтому вы не знали бы, что выполнение должно возобновиться в строке 4 (чтобы быть справедливым, у вас также будут другие проблемы :-D). Вот почему движок имеет собственную память, называемую «стеком». Он также поставляется с «кучей», но мы не будем говорить об этом здесь для простоты. По сути, стек помогает «JavaScript» отслеживать выполнение текущего скрипта.

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

foo ()
main (): 11

Итак, теперь «JavaScript» знает, что он должен выполнить «foo», но он также знает, что после выполнения «foo» выполнение должно продолжиться в «main» в строке 11 (конечно, стек не содержит эту строку номер но еще раз… Простота…).

Затем встречают призыв «бар»:

bar ()
foo (): 3
main (): 11

Итак, сейчас исполняется «бар». По достижении конца тела функции «bar ()» удаляется из стека, и движок берет значение наверху, чтобы знать, что делать дальше, поэтому выполнение возвращается обратно в «foo». в строке 3 и продолжается. Еще раз, когда выполняется «foo», он выскакивает из стека, и выполнение может возобновляться после строки «11» в «main ()».

Это идея, лежащая в основе режима выполнения «v8», где, как напоминание, асинхронность невозможна.

Хорошо, ну ... Но вы все еще не сказали мне, как я могу выполнять код асинхронно

Теперь, когда вы знаете, как работает стек, позвольте мне представить «цикл событий». Этот цикл на самом деле не является частью движка v8, но создается самим браузером. «Цикл событий» используется им для выполнения асинхронных задач.

Рассмотрим следующий код:

Как вы, наверное, знаете, функция «setTimeout» выполняет функцию через указанное количество миллисекунд, поэтому в приведенном выше коде отображается «foo start» и «foo finished» и только через 2 секунды после «bar выполнено», но как это работает? .

Представим выполнение этого скрипта в строке «4» этой замечательной картинкой:

«Цикл событий» не работает сам по себе, он использует очередь задач. По сути, идея состоит в том, что каждый асинхронный код, который необходимо выполнить, добавляется в очередь задач. Обратите внимание, что это очередь, а не стек, поэтому это не LIFO (последний пришел - первый ушел), а FIFO (первый пришел - первый ушел).

Так когда же тогда они будут выполнены?

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

Я до сих пор не понимаю роли «цикла событий»!

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

Замечательно! Красиво, просто и без проблем!

Что ж, хотя я согласен с замечательной, красивой и простой частью, я должен не согласиться с «беспроблемной» частью. Что ж, давайте проясним: проблем с «циклами событий» нет, но эта архитектура подразумевает кое-что важное: не доверяйте setTimeout / setInterval для критических задач. Например, рассмотрим следующий код:

Идея заключается в следующем:

  • Показать текущую дату.
  • Используйте «setTimeout», чтобы отобразить текущую дату на секунду позже.
  • Сделайте очень длинный расчет и покажите его.

Давайте выполним этот код и посмотрим, что по этому поводу скажет консоль:

Несмотря на то, что мы попросили setTimeout отображать дату через одну секунду после отображения первой даты, мы видим, что она отображается через 10 секунд после! Если вы правильно поняли объяснение «цикла событий», это не должно вызывать удивления. Действительно, проблема в том, что расчет занимает 10 секунд. Это означает, что во время этого выполнения стек не будет пустым, поскольку он все еще выполняет main (), поэтому «цикл событий» не сможет выбрать функцию обратного вызова из очереди задач для отображения даты. Только когда цикл for завершен, цикл событий может это сделать, то есть через 10 секунд. Следовательно, не следует доверять setTimeout / setInterval для выполнения критических задач.

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

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

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

У нас есть три функции, которые просто умножают свой первый аргумент на себя на определенное время (** означает ^). Забудьте о глупости этих функций и о том, что их можно объединить в одну функцию, просто сосредоточьтесь на том факте, что мы говорим, что выполнение вычислений на самом деле является асинхронной задачей (это не так, но, скажем так).

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

В консоли просто отобразится «25», и все готово. Однако представим, что вы хотите сделать (((5 ^ 2) ^ 3) ^ 4). Тогда код станет:

Вы получите верный результат, но код становится все более и более уродливым / сложным, особенно если, как здесь, вы используете одно и то же имя переменной несколько раз (хорошо, что это за результат ?!). Этот код оказался адом обратных вызовов.

Обещания

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

Теперь вы можете вызывать эти функции вот так.

Здорово, правда?

Подол… Ну как это лучше ?! Есть еще код, и мы все еще находимся в аду обратного вызова o_O

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

Ух ...

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

асинхронный / ожидание

async и await - ключевые слова (ES6), используемые для упрощения использования обещаний. Сделать функцию асинхронной так же просто, как добавить «async» перед ключевым словом «function»:

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

Я пробовал, но Google Chrome жалуется, что ожидание работает только в асинхронной функции.

Как указано в сообщении об ошибке, вы можете использовать «await» только в «асинхронной» функции, поэтому, если вы попытаетесь использовать его в основной функции «JavaScript», вы получите эту ошибку, поскольку основная функция не помечена как « асинхронный ». В большинстве случаев эта проблема не возникает, потому что ваш код будет вызываться событием DOM (или фреймворком «JavaScript»), и в этом случае вы сможете пометить его как «асинхронный».

Например:

Наблюдаемые

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

Если вам понравилась эта статья и вы хотите меня поддержать, не стесняйтесь хлопать в ладоши или покупать мне кофе :-)