Browser Event Loop
Модель конкуррентности в JS называется Event Loop и отличается от моделей конкурретности в таких языках как С++ и Java.
JS имеет однопточный рантайм, выполняющий одну вещь за раз. Каждая вкладка в браузере выполняется в отдельном процессе своим циклом событий (event loop - EL).
Этот цикл представляет собой очередь задач, в которую добавляются такие браузерные задачи как например:
Разбор HTML
Выполнение JS кода в
script
тегахОбработка реакций на действия пользователя (движения мыши, щелчки кнопок)
Обработка результатов (асинхронных) сетевых запросов.
Схематически, JS имеет следующий рантайм:
Выполнение функций формирует стек вызова.
Куча используется для хранения объектов (большой, неструктурированный регион памяти)
Очередь событий содержит множество задач на выполнение. Каждая задача - это отдельная функция. Когда стек вызовов пустеет, следующая задача берется из очереди событий на выполнение.
Очередь событий извлекает по одной задаче и выполняет её. EL использует последовательную (run-to-completion) семантику: текущая выполняемая задача всегда завершается полностью до того как следующая задача начнет выполняться. Таким образом, каждая задача имеет полный контроль над текущим состоянием программы и программисту нет волноваться по поводу конкуррентных модификаций. По завершению текущей задачи, из очереди событий извлекается следующая и передается на исполнение в JS-Runtime.

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

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 - для каждой задачи создается специальный объект, на котором регистрируются обработчики событий: один для успешного выполнения, другой для неудачного.
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-ов являются более сложные сигнатуры функций, более сложная обработка ошибок и усложненная композиция функций.
fs.readFile("myfile.txt", { encoding: "utf8" }, function(error, text) {
// (A)
if (error) {
// ...
}
console.log(text);
});
Стиль программирования с использованием callback-ов часто называют CPS (continuation-passing style), поскольку следуший шаг выполнения явно передается в качестве параметра. Пример CPS:
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
Сопутствующие библиотеки
Таймеры
JS имеет таймеры вида: setTimeout(callback, ms)
. Функция setTimeout
ожидает заданное число ms
а затем добавлет задачу по вычислению callback
в очередь. Стоит отметить, что ms
регулирует значение когда задача именно добавляется в EL, а не выполняется в нем.
setTimeout(function() {
// (A)
console.log("Second");
}, 0);
console.log("First"); // (B)
First;
Second;
requestAnimationFrame Функция requestAnimationFrame
позволяет координировать перерисовки в браузере.
Last updated
Was this helpful?