--- title: Параллельная модель и цикл событий. slug: Web/JavaScript/EventLoop tags: - Руководство translation_of: Web/JavaScript/EventLoop --- <p>{{JsSidebar("Advanced")}}</p> <p>Параллелизм/Многопоточность в JavaScript работает за счёт цикла событий (<strong>event loop</strong>), который отвечает за выполнение кода, сбора и обработки событий и выполнения под-задач из очереди (<strong>queued sub-tasks</strong>). Эта модель весьма отличается от других языков программирования, таких как C и Java.</p> <h2 id="Концепция_жизненного_цикла">Концепция жизненного цикла</h2> <p>В следующей секции объясняется теоретическая модель. Современные JavaScript движки внедряют/имплементируют и существенно оптимизируют этот процесс.</p> <h3 id="Визуальное_представление">Визуальное представление</h3> <p style="text-align: center;"><img alt="Stack, heap, queue" src="/files/4617/default.svg" style="height: 270px; width: 294px;"></p> <p style="text-align: center;">Для лучшего <strong>визуального</strong> представления работы <strong>Event loop</strong>, Вы можете ознакомиться с данным видео: <a href="https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=389s">https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=389s</a></p> <h3 id="Стек">Стек</h3> <p>Вызов любой функции создаёт контекст выполнения (<a href="https://wiki.developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide/In_depth#JavaScript_execution_contexts">Execution Context</a>). При вызове вложенной функции создаётся новый контекст, а старый сохраняется в специальной структуре данных - стеке вызовов (Call Stack).</p> <pre class="brush: js">function f(b) { var a = 12; return a + b + 35; } function g(x) { var m = 4; return f(m * x); } g(21); </pre> <p>Когда вызывается функция <code>g</code>, создаётся первый контекст выполнения, содержащий аргументы функции <code>g</code> и локальные переменные. Когда <code>g</code> вызывает <code>f</code>, создаётся второй контекст с аргументами <code>f</code> и её локальными переменными. И этот контекст выполнения <code>f</code> помещается в стек вызовов выше первого. Когда <code>f</code> возвращает результат, верхний элемент из стека удаляется. Когда <code>g</code> возвращает результат, её контекст также удалится, и стек становится пустым.</p> <h3 id="Куча">Куча</h3> <p>Объекты размещаются в <a href="https://ru.wikipedia.org/wiki/%D0%9A%D1%83%D1%87%D0%B0_(%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D1%8C)">куче</a>. Куча — это просто имя для обозначения большой неструктурированной области памяти.</p> <h3 id="Очередь">Очередь</h3> <p>Среда выполнения JavaScript содержит очередь задач. Эта очередь — список задач, подлежащих обработке. Каждая задача ассоциируется с некоторой функцией, которая будет вызвана, чтобы обработать эту задачу.</p> <p>Когда стек полностью освобождается, самая первая задача извлекается из очереди и обрабатывается. Обработка задачи состоит в вызове ассоциированной с ней функции с параметрами, записанными в этой задаче. Как обычно, вызов функции создаёт новый контекст выполнения и заносится в стек вызовов.</p> <p>Обработка задачи заканчивается, когда стек снова становится пустым. Следующая задача извлекается из очереди и начинается её обработка.</p> <h2 id="Цикл_событий">Цикл событий</h2> <p>Модель событийного цикла (<code>event loop</code>) называется так потому, что отслеживает новые события в цикле:</p> <pre class="brush: js">while(queue.waitForMessage()){ queue.processNextMessage(); }</pre> <p><code>queue.waitForMessage</code> ожидает поступления задач, если очередь пуста.</p> <h3 id="Запуск_до_завершения">Запуск до завершения</h3> <p>Каждая задача выполняется полностью, прежде чем начнёт обрабатываться следующая. Благодаря этому мы точно знаем: когда выполняется текущая функция – она не может быть приостановлена и будет целиком завершена до начала выполнения другого кода (который может изменить данные, с которыми работает текущая функция). Это отличает JavaScript от такого языка программирования как C. Поскольку в С функция, запущенная в отдельном потоке, в любой момент может быть остановлена, чтобы выполнить какой-то другой код в другом потоке.</p> <p>У данного подхода есть и минусы. Если задача занимает слишком много времени, то веб-приложение не может обрабатывать действия пользователя в это время (например, скролл или клик). Браузер старается смягчить проблему и выводит сообщение <em>"скрипт выполняется слишком долго" ("a script is taking too long to run")</em> и предлагает остановить его. Хорошей практикой является создание задач, которые исполняются быстро, и если возможно, разбиение одной задачи на несколько мелких.</p> <h3 id="Добавление_событий_в_очередь">Добавление событий в очередь</h3> <p>В браузерах события добавляются в очередь в любое время, если событие произошло, а так же если у него есть обработчик. В случае, если обработчика нет – событие потеряно. Так, клик по элементу, имеющему обработчик события по событию <code>click </code>, добавит событие в очередь, а если обработчика нет – то и событие в очередь не попадёт.</p> <p>Вызов <a href="/ru/docs/Web/API/WindowTimers/setTimeout" title="/en-US/docs/window.setTimeout">setTimeout</a> добавит событие в очередь по прошествии времени, указанного во втором аргументе вызова. Если очередь событий на тот момент будет пуста, то событие обработается сразу же, в противном случае событию функции <code>setTimeout</code> придётся ожидать завершения обработки остальных событий в очереди. Именно поэтому второй аргумент <code>setTimeout</code> корректно считать не временем, через которое выполнится функция из первого аргумента, а минимальное время, через которое она сможет выполниться.</p> <h3 id="Нулевые_задержки">Нулевые задержки</h3> <p>Нулевая задержка не даёт гарантии, что обработчик выполнится через ноль миллисекунд. Вызов {{domxref("WindowTimers.setTimeout", "setTimeout")}} с аргументом 0 (ноль) не завершится за указанное время. Выполнение зависит от количества ожидающих задач в очереди. Например, сообщение ''this is just a message'' из примера ниже будет выведено на консоль раньше, чем произойдёт выполнение обработчика <em>cb1</em>. Это произойдёт, потому что задержка – это минимальное время, которое требуется среде выполнения на обработку запроса.</p> <pre class="brush: js">(function () { console.log('this is the start'); setTimeout(function cb() { console.log('this is a msg from call back'); }); console.log('this is just a message'); setTimeout(function cb1() { console.log('this is a msg from call back1'); }, 0); console.log('this is the end'); })(); // "this is the start" // "this is just a message" // "this is the end" // "this is a msg from call back" // "this is a msg from call back1"</pre> <h3 id="Связь_нескольких_потоков_между_собой">Связь нескольких потоков между собой</h3> <p>Web Worker или кросс-доменный фрейм имеют свой собственный стек, кучу и очередь событий. Два отдельных событийных потока могут связываться друг с другом, только через отправку сообщений с помощью метода <code><a href="/en-US/docs/DOM/window.postMessage" title="/en-US/docs/DOM/window.postMessage">postMessage</a>. </code>Этот метод добавляет <code style="font-style: normal;">сообщение</code> в очередь другого, если он конечно принимает их.</p> <h2 id="Никогда_не_блокируется">Никогда не блокируется</h2> <p>Очень интересное свойство цикла событий в JavaScript, что в отличие от множества других языков, поток выполнения никогда не блокируется. Обработка I/O обычно осуществляется с помощью событий и колбэк-функций, поэтому даже когда приложение ожидает запрос от <a href="/ru/docs/IndexedDB" title="/en-US/docs/IndexedDB">IndexedDB</a> или ответ от <a href="/ru/docs/Web/API/XMLHttpRequest" title="/en-US/docs/DOM/XMLHttpRequest">XHR</a>, оно может обрабатывать другие процессы, например пользовательский ввод.</p> <p>Существуют хорошо известные исключения как <code style="font-style: normal;">alert</code> или синхронный XHR, но считается хорошей практикой избегать их использования.</p>