Каждый агент работает в одном потоке и имеет свой собственный стек вызовов (Execution Context Stack) и очередь задач (Job Queue).
Он может быть:
Веб-страницей в браузере
Web Worker-ом
Node.js процессом
Каждый агент имеет свой собственный Realm, который состоит из такой информации, как, например,
глобальный объект список прототипов и глобальные переменные
Кроме того, каждый агент управляет своими собственными:
Heap (of objects)
Queue (of jobs)
Stack (of execution contexts)
в HTML (и других языках) это и называется event loop, который позволяет асинхронно программировать в однопоточном
JavaScript
Microtask Queue — очередь микрозадач:
Promise.then/catch/finally
Функции, добавленные через queueMicrotask()
MutationObserver
Task Queue— крупные асинхронные задачи, как:
setTimeout, setInterval,
События DOM и их обработчики
JavaScript — однопоточный
JavaScript выполняет код, управляя его контекстом выполнения и стеком вызовов
Асинхронность достигается через Event Loop (события) и очереди задач
Нет sleep, встроенной функции, которая просто "приостанавливает" выполнение кода на какое-то время
Объект, реализующий Thenable интерфейс.
Имеет 3 состояния: pending, fulfilled, rejected.
Иммутабелен после перехода в fulfilled или rejected.
Это любой объект с методом then(), но не обязательно поддерживающий внутреннее состояние или спецификацию, как у промиса.
Может не поддерживать правильную логику для обработки состояний или асинхронных операций.
const notARealPromise = {
then: function(resolve, reject) {
// Не вызывает resolve или reject
console.log("Then called, but no resolve/reject");
}
};
notARealPromise.then(console.log); // Output: "Then called, but no resolve/reject"[[PromiseState]]: "pending", "fulfilled", "rejected"
[[PromiseResult]]: значение или ошибка
[[PromiseFulfillReactions]] очередь реакций на успешное выполнение / [[PromiseRejectReactions]] очередь реакций на отклонение
[[PromiseIsHandled]] для трекинга unhandled rejection
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("Success!"), 1000); // Промис будет разрешён через 1 секунду
});
// Первая реакция
promise.then(result => {
console.log("First reaction:", result); // добавляется в `[[PromiseFulfillReactions]]`
});
// Вторая реакция
promise.then(result => {
console.log("Second reaction:", result); // добавляется в `[[PromiseFulfillReactions]]`
});
Когда промис становится разрешённым (fulfilled) или отклонённым (rejected), его колбэки попадают в очередь реакций ([[PromiseFulfillReactions]] или [[PromiseRejectReactions]]).
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject("Failure!"); // Промис будет отклонён (rejected) через 1 секунду
}, 1000);
});
// Первая реакция на отклонение (попадает в `[[PromiseRejectReactions]]`)
promise
.catch(error => {
console.log("First rejected reaction:", error);
});
// Через второй аргумент в `.then()` вторая реакция (попадает в `[[PromiseRejectReactions]]`)
promise
.then(
result => {
console.log("This will never log, as the promise is rejected.");
},
error => {
console.log("Second rejected reaction:", error); // попадает в `[[PromiseRejectReactions]]`
}
);
// Третья реакция на отклонение (попадает в `[[PromiseRejectReactions]]`)
promise
.catch(error => {
console.log("Third rejected reaction:", error);
});
Эти колбэки не выполняются сразу. Они ставятся в очередь микротасков (microtask queue), а не в очередь обычных событий (таких как setTimeout).
Обработка микротасков происходит после завершения текущего синхронного кода, но до выполнения любых других асинхронных операций, таких как setTimeout, setInterval или другие..
let promise1 = new Promise((resolve) => {
resolve(new Promise((resolve) => {
resolve("Пример");
}));
});
promise1.then(result => {
console.log(result); // "Пример"
});Когда промис вызывает .then(), и результатом является другой промис:
let promise1 = new Promise((resolve) => {
resolve(promise1); // Промис ссылается сам на себя
});
promise1.then(result => {
console.log(result);
}).catch(error => {
console.log(error); // TypeError: Chaining cycle detected for promise
});Здесь промис promise1 ссылается на сам себя, что создает циклическую зависимость.
При попытке разрешить такой промис, возникает ошибка TypeError: Chaining cycle detected for promise.
Это предотвращает зацикливание в цепочке промисов.
Все методы возвращают новый Promise
then(onFulfilled, onRejected)
catch(fn) = then(null, fn)
finally(fn) вызывается всегда, но не меняет результат
Метод then используется для обработки успешного завершения промиса. Он принимает два аргумента:
fulfilled).rejected).Метод catch используется для обработки ошибок, возникших в процессе работы с промисами. Он перехватываетисключения, выброшенные как в самом промисе, так и в любой функции, использующей then.
Важно, что catch будет ловить любые ошибки, если они не были обработаны внутри промиса через второй аргумент then.
new Promise((_, reject) => reject('Ошибка'))
.catch(error => {
console.log(error); // 'Ошибка'
});Метод finally выполняет код, который должен быть выполнен в любом случае, независимо от того, был ли промис разрешен или отклонен. Это полезно для выполнения действий, которые должны происходить всегда (например, очистка ресурсов, закрытие соединений и т. д.).
async/await — это синтаксический сахар над промисами. С ними асинхронный код выглядит как синхронный, что делает его понятнее и читаемее, официально добавлены в ECMAScript 2017 (ES8).
Promise.resolve(10).then(value => {
console.log(value); // 10
});
async function bar() {
let value = await Promise.resolve(10);
console.log(value); // 10
}Любая функция, помеченная как async, всегда возвращает промис. Даже если в ней не используется await, она все равно будет возвращать промис
async function myFunction() {
return 42; // Это автоматически будет wrapped в Promise
}
myFunction().then(console.log); // Выведет 42Ключевое слово await используется внутри async-функции и ожидает разрешения (или отклонения) промиса. Он "приостанавливает" выполнение текущей async-функции до получения результата.
Под капотом — то же, что и then, но с паузой в стейте выполнения
Внутри async/await все работает через промисы, просто синтаксис делает его более читаемым и последовательным. Использование await заменяет цепочку .then(), делая код более похожим на синхронный, но асинхронный.
async function example() {
const result = await new Promise(resolve => setTimeout(() => resolve('Привет!'), 1000));
console.log(result); // Выведет 'Привет!' через 1 секунду
}
example();
JS превращает функцию в стейт-машину
Когда компилятор видит async-функцию, он фактически превращает её в стейт-машину, которая выполняется поэтапно. Каждый раз, когда код встречает await, выполнение функции "приостанавливается", и управление передается обратно в цикл событий.
На каждой итерации await:
await до тех пор, пока промис не будет разрешен или отклонен.Promise, который будет разрешен значением или ошибкой.Что может быть передано в await:
Promise — будет ждать разрешения или отклонения.
Любой thenable — объект, у которого есть метод .then(). Это может быть не только объект Promise, но и любой объект, реализующий интерфейс thenable.
Обычное значение — сразу возвращает значение без ожидания.
//Внутри await, движок делает что-то вроде:
Promise.resolve(значение)
await 42
/// ====>
await Promise.resolve(42)Если значение — обычное (например, 42), оно сразу возвращается, но await всё равно делает паузу до следующей микротаски.
PromiseКак работает: Каждый запрос выполняется по очереди, следующий начинается только после завершения предыдущего.
Минусы:
Медленно. Все запросы выполняются один за другим.
Неэффективно. Блокировка на каждом запросе.
for (const url of urls) {
await fetch(url); // последовательно!
}Как работает: Все запросы отправляются одновременно, Promise.all ждёт их завершения
await Promise.all(urls.map(fetch));
// параллельноПреимущества:
Быстрее. Все запросы выполняются одновременно.
Эффективно. Максимальное использование ресурсов сети и сервера.
Минусы:
Могут возникнуть проблемы с ресурсами при слишком большом количестве параллельных запросов.
Ошибки в одном запросе прерывают всю операцию (нужна обработка ошибок).
Когда создается промис, его выполнение начинается немедленно, и отменить его невозможно. Промис представляет собой результат асинхронной операции, которая либо завершится успешно, либо с ошибкой. У него нет встроенной логики для остановки или отмены операции.
Это объект, который позволяет отменить асинхронную операцию, такую как запросы с fetch.
Он может использоваться для отмены асинхронных операций, если они не завершились вовремя или если они больше не нужны.
const controller = new AbortController();
const signal = controller.signal;
fetch(url, { signal }) // Передаем signal в fetch для отслеживания отмены
.then(response => {
// обработка ответа
})
.catch(err => {
if (err.name === 'AbortError') {
console.log('Запрос был отменен');
}
});
// Отмена запроса
controller.abort();
Обертка для промиса, которая будет отслеживать отмену через флаг или сигнал.
class CancellablePromise {
constructor(executor) {
this.cancelled = false;
this.promise = new Promise((resolve, reject) => {
executor(resolve, reject);
});
}
cancel() {
this.cancelled = true;
}
then(onFulfilled, onRejected) {
return this.promise.then(
value => (this.cancelled ? Promise.reject('Cancelled') : onFulfilled(value)),
onRejected
);
}
}
// Пример использования:
const cancelable = new CancellablePromise((resolve, reject) => {
setTimeout(() => resolve('Done'), 2000);
});
cancelable.then(result => console.log(result));
cancelable.cancel(); // Останавливает выполнение
Спецификация JavaScript не предоставляет стандартного способа обработки необработанных отклонений промисов. То, как эти отклонения обрабатываются, зависит от среды выполнения (например, Node.js или браузер).
Ранее: В более ранних версиях Node.js необработанное отклонение промиса приводило к крашу процесса.
Сейчас: Node.js генерирует предупреждение, если промис отклоняется, но его ошибка не обрабатывается (например, через .catch() или try-catch).
process.on('unhandledRejection', (reason, promise) => {
console.log('Необработанное отклонение промиса:', reason);
// Здесь можно предпринять действия, например, логировать ошибку
});
Chrome, Firefox, Safari, Edge поддерживают window.onunhandledrejection для глобальной обработки необработанных отклонений.
window.onunhandledrejection = (event) => {
console.log('Необработанное отклонение промиса:', event.reason);
// Здесь можно выполнить действия, например, логировать ошибку
};