# Browser Event Loop

Модель конкуррентности в JS называется **Event Loop** и отличается от моделей конкурретности в таких языках как С++ и Java.&#x20;

JS имеет однопточный рантайм, выполняющий одну вещь за раз. Каждая вкладка в браузере выполняется в отдельном процессе своим циклом событий (*event loop* - EL).

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

* Разбор HTML
* Выполнение JS кода в `script` тегах
* Обработка реакций на действия пользователя (движения мыши, щелчки кнопок)
* Обработка результатов (асинхронных) сетевых запросов.

Схематически, JS имеет следующий рантайм:

![](https://1356653101-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M5bu9Zoywb01ca-XJjd%2F-MFL3zv-gudm9Gd-VLhG%2F-MFL7fTP8C_i6yi0l2Os%2Fjs-runtime.svg?alt=media\&token=4cd4a5ae-30f6-43ee-bd89-4885db70efa4)

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

Очередь событий извлекает по одной задаче и выполняет её. EL использует последовательную (*run-to-completion*) семантику: текущая выполняемая задача всегда завершается полностью до того как следующая задача начнет выполняться. Таким образом, каждая задача имеет полный контроль над текущим состоянием программы и программисту нет волноваться по поводу конкуррентных модификаций. По завершению текущей задачи, из очереди событий извлекается следующая и передается на исполнение в JS-Runtime.

![](https://1356653101-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M5bu9Zoywb01ca-XJjd%2F-MFL3zv-gudm9Gd-VLhG%2F-MFLAISWk2T7OgqJ44is%2Fevent-loop-phases.png?alt=media\&token=a8592b9b-fdcd-4374-871e-0632b5b37b03)

Параллельно с EL выполняется множество других фоновых процессов (таймеры, обработка ввода), которые взаимодействуют с EL помещая в его очередь задач новые задачи на выполнение. Эти фоновые процессы запускаются как самим браузером, так и из текущего пользовательского кода. Например, выполнение запроса из JS-кода создает фоновую задачу, выполняюуюся в *WebApi* браузера. По завершению выполнения запроса WebApi помещает задачу обработки запроса в EL.

Все задачи, связанные с JS (script и сетевые запросы) выполняются внутри встроенного в браузер JS-движка. Js-задачи завершаются вместе с завершением выполнения соответствующего им кода. JS-движок обеспечивает выполнение JS-кода, хранит стек вызовов, управляет кучей и т.п.

![](https://1356653101-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M5bu9Zoywb01ca-XJjd%2F-MFL3zv-gudm9Gd-VLhG%2F-MFLA8WrCBgPNvCDasuw%2Fjs-async-event-loop.jpg?alt=media\&token=13b3884c-8fc1-4d0e-9641-67419cd1ded3)

A web worker or a cross-origin iframe has its own stack, heap, and message queue. Two distinct runtimes can only communicate through sending messages via the postMessage method. This method adds a message to the other runtime if the latter listens to message events.

### Asynchronous Primitives

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

Избежать блокирования позволяют различные методы асинхронного программирования и библиотеки:

* **Worker API** - позволяет вынести длительные вычисления в отдельный параллельный процесс.
* **Asynchronous result** - выполняя задачу, программист оставляет callback или другую функцию нотификации, которая будет вызвана когда задача завершится. Например, `setTimeout` это асинхронный `sleep`.

Существует два основных способа уведомления об асинхронном завершении задачи:

* **Events** - для каждой задачи создается специальный объект, на котором регистрируются обработчики событий: один для успешного выполнения, другой для неудачного.

```javascript
var req = new XMLHttpRequest();
req.open("GET", url);

req.onload = function() {
  if (req.status == 200) {
    processData(req.response);
  } else {
    console.log("ERROR", req.statusText);
  }
};

req.onerror = function() {
  console.log("Network Error");
};

req.send(); // Add request to task queue
```

* **Callbacks** - это функции, которые передаются для вызова в том случае, когда выполнение асинхронной операции завершилось. Недостаком Callback-ов являются более сложные сигнатуры функций, более сложная обработка ошибок и усложненная композиция функций.

```javascript
fs.readFile("myfile.txt", { encoding: "utf8" }, function(error, text) {
  // (A)
  if (error) {
    // ...
  }
  console.log(text);
});
```

Стиль программирования с использованием callback-ов часто называют CPS (*continuation-passing style*), поскольку следуший шаг выполнения явно передается в качестве параметра. Пример CPS:

```javascript
console.log("A");
identity("B", function step2(result2) {
  console.log(result2);
  identity("C", function step3(result3) {
    console.log(result3);
  });
  console.log("D");
});
console.log("E");

// Output: A E B D C

function identity(input, callback) {
  setTimeout(function() {
    callback(input);
  }, 0);
}
```

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

* `Promise`
* `Generators`
* Сопутствующие библиотеки

**Таймеры**&#x20;

JS имеет таймеры вида: `setTimeout(callback, ms)`. Функция `setTimeout` ожидает заданное число `ms` а затем добавлет задачу по вычислению `callback` в очередь. Стоит отметить, что `ms` регулирует значение когда задача именно добавляется в EL, а не выполняется в нем.

```javascript
setTimeout(function() {
  // (A)
  console.log("Second");
}, 0);
console.log("First"); // (B)

First;
Second;
```

**requestAnimationFrame** Функция `requestAnimationFrame` позволяет координировать перерисовки в браузере.
