Событийный цикл и итерация
Событийный цикл, микро- и макрозадачи, итераторы и итерируемые объекты, генераторы и реактивные потоки.
8 вопросов
JuniorТеорияОчень частоЧто такое event loop и как он делает однопоточный JS неблокирующим?
Что такое event loop и как он делает однопоточный JS неблокирующим?
JavaScript работает в одном потоке с одним стеком вызовов. Async-работа (таймеры, I/O, события) передаётся хосту, чьи колбэки попадают в очередь задач. Event loop постоянно ждёт опустошения стека, затем извлекает одну задачу и выполняет её до конца. Это держит поток неблокирующим: долгая async-работа идёт вне потока и никогда не замораживает стек.
Типичные ошибки
- ✗Думать, что event loop выполняет колбэки параллельно — он строго однопоточный
- ✗Считать, что задача может вытеснить уже выполняющийся на стеке код
- ✗Полагать, что колбэк
setTimeout(fn, 0)выполняется сразу, а не после опустошения стека
Уточняющие вопросы
- →Где на самом деле выполняются таймеры и I/O, если JS-поток однопоточный?
- →Почему колбэк
setTimeout(fn, 0)может задержаться сильно дольше 0 мс?
MiddleТеорияОчень частоЧем различаются микрозадачи и макрозадачи в порядке event loop?
Чем различаются микрозадачи и макрозадачи в порядке event loop?
Колбэки промисов (.then/.catch/.finally, продолжения await) и queueMicrotask идут в очередь микрозадач; setTimeout/setInterval и колбэки I/O — макрозадачи. После каждой макрозадачи (и начального скрипта) цикл полностью опустошает очередь микрозадач перед следующей макрозадачей. Поэтому микрозадачи всегда выполняются раньше следующего setTimeout, а микрозадача, поставленная другой микрозадачей, выполнится в том же опустошении.
Типичные ошибки
- ✗Думать, что
setTimeout(fn, 0)выполняется раньше поставленного колбэкаPromise.then - ✗Считать, что цикл выполняет одну микрозадачу на макрозадачу, а не опустошает очередь целиком
- ✗Воспринимать микро- и макрозадачи как одну общую FIFO-очередь по порядку добавления
Уточняющие вопросы
- →Почему бесконечная перепостановка микрозадач может заморить макрозадачи и рендеринг?
- →Куда попадает продолжение
await— в микро- или макрозадачу — и почему?
JuniorТеорияИногдаЧто делает объект итерируемым и как for...of его перебирает?
Что делает объект итерируемым и как for...of его перебирает?
Объект итерируемый, если у него есть метод [Symbol.iterator](), возвращающий итератор — объект с методом next(), отдающим { value, done }. for...of вызывает [Symbol.iterator](), затем многократно вызывает next(), пока done не станет true, связывая каждое value. Массивы, строки, Map и Set — встроенные итерируемые; обычные объекты — нет.
Типичные ошибки
- ✗Думать, что обычные объекты итерируются через
for...of— у них нет[Symbol.iterator] - ✗Путать
for...of(значения через итератор) сfor...in(перечислимые ключи) - ✗Считать, что итератор возвращает целый массив, а не по одному шагу
{ value, done }
Уточняющие вопросы
- →Как сделать обычный объект итерируемым, добавив
[Symbol.iterator]? - →Как spread-синтаксис и деструктуризация массива опираются на тот же протокол итерации?
MiddleТеорияИногдаЧто такое генераторная функция и как она связана с итераторами?
Что такое генераторная функция и как она связана с итераторами?
Генератор — это функция, объявленная через function*, которая может приостанавливаться и возобновляться. Каждый yield выдаёт значение и приостанавливает выполнение до следующего вызова next(). Вызов генератора возвращает итератор (он же итерируемый), поэтому он работает напрямую с for...of и spread. Значения создаются лениво по запросу, позволяя моделировать бесконечные или дорогие последовательности без полного предвычисления.
Типичные ошибки
- ✗Думать, что вызов генератора выполняет его тело — он лишь возвращает приостановленный итератор
- ✗Считать, что все значения вычисляются заранее, а не лениво на каждый
next() - ✗Забывать, что возвращённый генератором объект — и итератор, и итерируемый
Уточняющие вопросы
- →Как передать значение обратно в генератор через
next(value)наyield? - →Как генераторы позволяют моделировать бесконечную последовательность без бесконечной памяти?
SeniorТеорияИногдаРазберите полный порядок event loop, включая Node setImmediate и process.nextTick.
Разберите полный порядок event loop, включая Node setImmediate и process.nextTick.
За оборот: выполнить синхронный код, затем полностью опустошить очередь микрозадач (промисы), затем выполнить одну макрозадачу (setTimeout, I/O), затем снова опустошить микрозадачи. Бесконечная перепостановка микрозадач морит макрозадачи и блокирует рендеринг. В Node process.nextTick идёт раньше микрозадач промисов; setImmediate срабатывает в фазе check после I/O, а setTimeout(fn, 0) — в более ранней фазе timers, поэтому их порядок зависит от контекста.
Типичные ошибки
- ✗Думать, что цикл чередует одну микрозадачу на макрозадачу, а не опустошает микрозадачи целиком
- ✗Считать, что
process.nextTickидёт после микрозадач промисов, а не раньше них - ✗Полагать, что
setImmediateиsetTimeout(fn, 0)всегда срабатывают в фиксированном порядке
Уточняющие вопросы
- →Почему рекурсивный
process.nextTickопаснее для голодания I/O, чем рекурсивныйsetImmediate? - →Почему внутри колбэка I/O
setImmediateнадёжно опережаетsetTimeout(fn, 0)?
MiddleТеорияРедкоЧто такое RxJS, и что такое его операторы и подписки?
Что такое RxJS, и что такое его операторы и подписки?
RxJS — это библиотека, реализующая observable для композиции асинхронных и событийных потоков. Операторы вроде map, filter и debounceTime — чистые функции, берущие исходный поток и возвращающие новый производный, соединяемые через pipe. Вызов subscribe запускает поток и возвращает подписку, чей unsubscribe освобождает её и прекращает выдачу значений.
Типичные ошибки
- ✗Думать, что операторы мутируют источник, а не возвращают новый производный поток
- ✗Считать, что поток RxJS завершается раз как промис, а не выдаёт много значений
- ✗Забывать
unsubscribe, утекая подпиской и её источником
Уточняющие вопросы
- →Почему забытый
unsubscribeприводит к утечке памяти? - →Как
debounceTimeменяет тайминг выдачи значений исходного потока?
SeniorТеорияРедкоЧто такое асинхронные генераторы и как async/await концептуально связаны с генераторами?
Что такое асинхронные генераторы и как async/await концептуально связаны с генераторами?
Асинхронный генератор (async function*) выдаёт значения, готовность которых сама асинхронна: каждый next() возвращает промис { value, done }, и вы потребляете его через for await...of, который ждёт каждый шаг. Он может и await внутри, и yield наружу, что идеально для потоковой async-передачи данных. Концептуально async/await — это генераторы плюс промисы: await — это yield промиса, который драйвер разрешает и подаёт обратно через next().
Типичные ошибки
- ✗Думать, что
for await...ofработает по синхронному итерируемому, а не по асинхронному - ✗Считать, что асинхронный генератор сразу возвращает массив, а не стримит лениво
- ✗Не видеть в
async/awaitконцептуально генераторы, управляемые разрешателем промисов
Уточняющие вопросы
- →Как
for await...ofждёт промис каждогоnext()перед телом цикла? - →Как реализовать
async/awaitповерх генератора и драйвера промисов?
SeniorТеорияРедкоОбъясните горячие и холодные observable, backpressure и их сравнение с асинхронными итераторами.
Объясните горячие и холодные observable, backpressure и их сравнение с асинхронными итераторами.
Холодный observable запускает свежий источник на каждого подписчика (unicast); горячий разделяет один уже работающий источник, поэтому поздние подписчики пропускают ранние значения (multicast). Backpressure — это когда быстрый push-источник обгоняет медленного потребителя; во встроенном RxJS нет управления потоком, поэтому буферизуют, троттлят или сэмплируют. Асинхронные итераторы pull-ориентированы — for await запрашивает по одному значению — давая естественный backpressure в отличие от push-observable.
Типичные ошибки
- ✗Путать горячий и холодный — холодный unicast на подписчика, горячий — общий multicast
- ✗Думать, что RxJS решает backpressure сам, а не через буфер или троттлинг
- ✗Считать асинхронные итераторы push-ориентированными, тогда как
for awaitтянет по одному
Уточняющие вопросы
- →Как
Subjectпревращает холодный observable в горячий multicast? - →Почему pull-ориентированный асинхронный итератор даёт backpressure бесплатно?