← Назад к вопросам

Бесконечно ли растет очередь задач

2.2 Middle🔥 141 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Бесконечно ли растет очередь задач (Event Loop)

Это отличный вопрос про Event Loop в JavaScript. Не бесконечно - очередь может расти, но не навечно. Давайте разберёмся как это работает.

Что такое очередь задач

В JavaScript есть несколько типов очередей:

1. Call Stack (стек вызовов) Выполняет синхронный код по одному

2. Microtask Queue (очередь микротасков) Промисы, queueMicrotask(), MutationObserver

3. Macrotask Queue (очередь макротасков) setTimeout, setInterval, setImmediate, I/O

4. Animation Frame Queue requestAnimationFrame

Порядок выполнения (Event Loop)

1. Выполни весь синхронный код (Call Stack)
2. Выполни ВСЕ микротаски (полностью очищаю микротаск очередь)
3. Выполни ОДИН макротаск
4. Выполни ВСЕ микротаски (снова!)
5. Вернись на шаг 3

Может ли очередь расти бесконечно?

Ответ: НЕТ, если код правильный

// Синхронный код - выполнится один раз
for (let i = 0; i < 5; i++) {
  console.log(i);
}
// 0, 1, 2, 3, 4

// Микротаски - выполнятся все, потом очередь пуста
Promise.resolve().then(() => console.log('micro1'));
Promise.resolve().then(() => console.log('micro2'));
// micro1, micro2

// Макротаски - выполнятся по одному
setTimeout(() => console.log('macro1'), 0);
setTimeout(() => console.log('macro2'), 0);
// macro1
// macro2

Но очередь МОЖЕТ расти, если...

1. Бесконечный цикл добавления промисов

// ПЛОХО - рекурсивные промисы
function infiniteLoop() {
  Promise.resolve().then(() => {
    console.log('Adding another promise');
    infiniteLoop(); // добавляем новый промис
  });
}

infiniteLoop();
// Это ЗАМОРОЗИТ браузер!
// Микротаски бесконечно добавляют новые микротаски
// Main thread никогда не доберётся до макротасков

2. Бесконечные setTimeout

// ПЛОХО - вызываем setTimeout из setTimeout
function recursiveTimeout() {
  console.log('Добавляю новый timeout');
  setTimeout(recursiveTimeout, 0);
  // Каждый раз добавляем новый timeout
}

recursiveTimeout();
// Очередь будет расти, но браузер всё ещё может ответить
// (в отличие от микротасков)

3. Добавление множества слушателей событий

// ПЛОХО - добавляем слушатели в loop
for (let i = 0; i < 1000000; i++) {
  element.addEventListener('click', handler);
}
// Очередь событий растёт

Демонстрация вспомогательных микротасков

Console.log('Start');

// Добавляем микротаски
Promise.resolve()
  .then(() => {
    console.log('Micro 1');
    // Добавляем ещё микротаски
    return Promise.resolve();
  })
  .then(() => {
    console.log('Micro 2');
  });

// Добавляем макротаск
setTimeout(() => {
  console.log('Macro 1');
}, 0);

console.log('End');

// Вывод:
// Start
// End
// Micro 1
// Micro 2
// Macro 1

Когда очередь ДЕЙСТВИТЕЛЬНО растёт

Пример из реальной жизни:

// Бесконечное добавление в микротаск очередь
let count = 0;

function addMicrotask() {
  Promise.resolve().then(() => {
    count++;
    console.log('Microtask:', count);
    if (count < 10) {
      addMicrotask(); // рекурсивно добавляем ещё
    }
  });
}

addMicrotask();
// Выполнится:
// Microtask: 1
// Microtask: 2
// Microtask: 3
// ... до 10

Всё выполнится, потом очередь пуста.

Когда очередь растёт ОПАСНО

// ОПАСНО - бесконечное добавление микротасков
function dangerousLoop() {
  Promise.resolve().then(() => {
    console.log('Running');
    dangerousLoop(); // добавляем снова
  });
}

dangerousLoop();
// Это замораживает интерпретатор
// Микротаски будут создаваться бесконечно
// Main thread никогда не будет свободен

Оптимизация: не допускать роста очереди

1. Избегай рекурсивных микротасков

// Плохо
function bad() {
  Promise.resolve().then(() => bad());
}

// Хорошо - используй setTimeout вместо Promise
function good() {
  setTimeout(() => good(), 0);
}

2. Используй batch обновления

// Плохо - обновляем DOM каждый раз
for (let i = 0; i < 1000; i++) {
  document.body.innerHTML += '<div>' + i + '</div>';
}

// Хорошо - соберём всё в массив, добавим один раз
const html = [];
for (let i = 0; i < 1000; i++) {
  html.push('<div>' + i + '</div>');
}
document.body.innerHTML = html.join('');

3. Правильно управляй асинхронными операциями

// Плохо - может добавиться бесконечное количество запросов
async function badFetch() {
  const data = await fetch('/api');
  await badFetch(); // рекурсивный запрос
}

// Хорошо - контролируй количество
async function goodFetch(limit = 0) {
  if (limit >= 10) return;
  const data = await fetch('/api');
  await goodFetch(limit + 1);
}

Практический тест

const div = document.getElementById('test');
let microtaskCount = 0;
let macrotaskCount = 0;

// Добавляем 3 макротаски
for (let i = 0; i < 3; i++) {
  setTimeout(() => {
    macrotaskCount++;
    console.log(`Macro ${macrotaskCount}`);
    
    // Внутри каждого макротаска добавляем 2 микротаски
    for (let j = 0; j < 2; j++) {
      Promise.resolve().then(() => {
        microtaskCount++;
        console.log(`  Micro ${microtaskCount}`);
      });
    }
  }, 0);
}

// Вывод:
// Macro 1
//   Micro 1
//   Micro 2
// Macro 2
//   Micro 3
//   Micro 4
// Macro 3
//   Micro 5
//   Micro 6

Итог

Очередь задач НЕ растет бесконечно в нормальном коде:

  • Синхронный код выполнится и пропадёт
  • Микротаски выполнятся все, потом очистится
  • Макротаски выполнятся по одному

Но может расти если:

  1. Рекурсивно добавляешь промисы (замораживает браузер)
  2. Бесконечно вызываешь setTimeout
  3. Добавляешь множество слушателей событий

Правило: никогда не создавай рекурсивные микротаски или макротаски без базового случая. Всегда думай о том, когда очередь перестанет расти.