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

Как распределяются по очереди задач синхронный код?

2.0 Middle🔥 171 комментариев
#Soft Skills и рабочие процессы

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

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

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

Как распределяются по очереди задач синхронный код

Этот вопрос касается Event Loop в JavaScript — механизма, который управляет выполнением кода, обработкой асинхронных операций и отрисовкой интерфейса. Понимание Event Loop критично для оптимизации производительности.

Call Stack и Event Loop

JavaScript — однопоточный язык. Все операции выполняются в единственном потоке через Call Stack:

function a() {
  console.log('a');
  b();
  console.log('a завершена');
}

function b() {
  console.log('b');
}

a();

// Вывод:
// a
// b
// a завершена

Порядок выполнения:

  1. a() добавляется в стек
  2. Выполняется console.log('a')
  3. b() добавляется в стек
  4. Выполняется console.log('b')
  5. b() удаляется из стека
  6. Выполняется console.log('a завершена')
  7. a() удаляется из стека

Очередь микротасок (Microtask Queue)

После выполнения синхронного кода JavaScript обрабатывает все микротаски перед отрисовкой:

console.log('Синхронный код: 1');

// Микротаски: Promise
Promise.resolve()
  .then(() => console.log('Микротаска: Promise'));

// Микротаски: queueMicrotask
queueMicrotask(() => {
  console.log('Микротаска: queueMicrotask');
});

console.log('Синхронный код: 2');

// Вывод:
// Синхронный код: 1
// Синхронный код: 2
// Микротаска: Promise
// Микротаска: queueMicrotask

Очередь макротасок (Macrotask Queue)

Макротаски выполняются после всех микротасок и обычно в одной за раз:

console.log('Старт');

setTimeout(() => {
  console.log('setTimeout 1 (макротаска)');
  
  // Это обещание выполнится ДО следующей макротаски
  Promise.resolve().then(() => {
    console.log('Promise после setTimeout');
  });
}, 0);

setTimeout(() => {
  console.log('setTimeout 2 (макротаска)');
}, 0);

Promise.resolve().then(() => {
  console.log('Promise (микротаска)');
});

console.log('Конец');

// Вывод:
// Старт
// Конец
// Promise (микротаска)
// setTimeout 1 (макротаска)
// Promise после setTimeout
// setTimeout 2 (макротаска)

Иерархия выполнения в Event Loop

┌─────────────────────────────────────┐
│ 1. Выполнить синхронный код         │
│    (Call Stack пуст)                │
└─────────────────────────────────────┘
           |
           v
┌─────────────────────────────────────┐
│ 2. Обработать ВСЕ микротаски        │
│    - Promise                        │
│    - queueMicrotask                 │
│    - MutationObserver               │
│    (пока очередь не пуста)          │
└─────────────────────────────────────┘
           |
           v
┌─────────────────────────────────────┐
│ 3. Отрисовать (Rendering)           │
│    - Обновить DOM                   │
│    - Пересчитать стили              │
│    - Перерисовать страницу          │
└─────────────────────────────────────┘
           |
           v
┌─────────────────────────────────────┐
│ 4. Обработать ОДНУ макротаску       │
│    - setTimeout                     │
│    - setInterval                    │
│    - setImmediate                   │
│    - I/O операции                   │
└─────────────────────────────────────┘
           |
           v
       Вернуться на шаг 2

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

console.log('1. Начало');

setTimeout(() => {
  console.log('2. setTimeout');
  
  Promise.resolve().then(() => {
    console.log('3. Promise в setTimeout');
  });
}, 0);

Promise.resolve()
  .then(() => {
    console.log('4. Promise then 1');
    return Promise.resolve();
  })
  .then(() => {
    console.log('5. Promise then 2');
  });

queueMicrotask(() => {
  console.log('6. queueMicrotask');
});

console.log('7. Конец');

// Порядок выполнения:
// 1. Начало      (синхронный код)
// 7. Конец       (синхронный код)
// 4. Promise then 1 (микротаска)
// 6. queueMicrotask (микротаска)
// 5. Promise then 2 (микротаска - цепочка)
// 2. setTimeout  (макротаска)
// 3. Promise в setTimeout (микротаска)

requestAnimationFrame

requestAnimationFrame имеет особую позицию — выполняется ПЕРЕД отрисовкой:

console.log('Синхронный код');

setTimeout(() => {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(() => {
  console.log('Promise');
});

requestAnimationFrame(() => {
  console.log('requestAnimationFrame');
});

// Вывод:
// Синхронный код
// Promise
// requestAnimationFrame
// setTimeout

Практические примеры оптимизации

Проблема: блокирующий синхронный код

// Плохо: заблокирует интерфейс
for (let i = 0; i < 1000000; i++) {
  // Тяжелые вычисления
  const result = Math.sqrt(i) * Math.sin(i);
}

// Хорошо: разбить на части
function processInChunks(count, chunkSize) {
  let i = 0;
  
  function processChunk() {
    const end = Math.min(i + chunkSize, count);
    
    while (i < end) {
      const result = Math.sqrt(i) * Math.sin(i);
      i++;
    }
    
    if (i < count) {
      // Отпустить главный поток
      setTimeout(processChunk, 0);
    }
  }
  
  processChunk();
}

processInChunks(1000000, 10000);

Оптимизация с микротасками

// Батчинг обновлений состояния
class StateManager {
  constructor() {
    this.updates = [];
    this.scheduled = false;
  }
  
  update(fn) {
    this.updates.push(fn);
    
    if (!this.scheduled) {
      this.scheduled = true;
      queueMicrotask(() => this.flush());
    }
  }
  
  flush() {
    const updates = this.updates;
    this.updates = [];
    this.scheduled = false;
    
    // Применяем все обновления сразу
    updates.forEach(fn => fn());
  }
}

Рекомендации

  • Микротаски быстрее — используйте Promise вместо setTimeout для срочных операций
  • requestAnimationFrame идеален для DOM манипуляций и анимаций
  • Избегайте длинного синхронного кода — разбивайте на макротаски
  • Батчируйте обновления через микротаски для оптимизации
  • Помните о порядке — микротаски выполняются перед отрисовкой
Как распределяются по очереди задач синхронный код? | PrepBro