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

Как поставить микрозадачу в очередь?

3.0 Senior🔥 111 комментариев
#JavaScript Core#Браузер и сетевые технологии

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

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

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

Микрозадачи (Microtasks) в JavaScript

Микрозадачи — это асинхронные операции, которые выполняются с более высокий приоритетом, чем макротаски (setTimeout, setInterval). Они выполняются в конце текущего события.

Что такое Event Loop

Чтобы понять микрозадачи, нужно разобраться в Event Loop:

1. Синхронный код
2. Микротаски (Promise, queueMicrotask)
3. Макротаски (setTimeout, setInterval)
4. Рендеринг (если нужно)
5. Повторить

Как поставить микрозадачу в очередь

1. Promise (самый частый способ)

// Promise автоматически создает микрозадачу
Promise.resolve()
  .then(() => {
    console.log('Микрозадача 1');
  })
  .then(() => {
    console.log('Микрозадача 2');
  });

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

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

// Результат:
// Синхронный код
// Микрозадача 1
// Микрозадача 2
// Макротаска

2. queueMicrotask (явный способ)

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

queueMicrotask(() => {
  console.log('Микрозадача');
});

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

// Результат:
// Старт
// Конец
// Микрозадача

3. MutationObserver

// MutationObserver также использует микротаски
const observer = new MutationObserver(() => {
  console.log('DOM изменился');
});

observer.observe(document.body, { childList: true });

document.body.appendChild(document.createElement('div'));

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

1. Батчинг обновлений состояния

export function useBatchUpdate() {
  const [state, setState] = useState(0);

  const batchUpdate = () => {
    // Все обновления в одной микротаске
    queueMicrotask(() => {
      setState(prev => prev + 1);
      setState(prev => prev + 1);
      setState(prev => prev + 1);
      // React батчит эти обновления
      // Результат: одно переполнение
    });
  };

  return { state, batchUpdate };
}

2. Отложенное выполнение после синхронного кода

function processData(data) {
  // Синхронная обработка
  const result = expensiveCalculation(data);
  
  // Отложить уведомление до конца текущего события
  queueMicrotask(() => {
    // Это выполнится ДО setTimeout
    notify('Обработка завершена');
  });
  
  return result;
}

processData(largeData);
console.log('Не заблокированы!');

3. Реактивные зависимости (Reactive)

class Reactive {
  constructor(initialValue) {
    this.value = initialValue;
    this.subscribers = [];
    this.pendingNotifications = false;
  }

  subscribe(callback) {
    this.subscribers.push(callback);
  }

  setValue(newValue) {
    this.value = newValue;
    
    // Собрать все уведомления в микротаску
    if (!this.pendingNotifications) {
      this.pendingNotifications = true;
      queueMicrotask(() => {
        this.notifySubscribers();
        this.pendingNotifications = false;
      });
    }
  }

  notifySubscribers() {
    this.subscribers.forEach(cb => cb(this.value));
  }
}

const count = new Reactive(0);
count.subscribe(val => console.log('Updated:', val));

count.setValue(1);
count.setValue(2);
count.setValue(3);

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

// Результат:
// Updated: 3 (все изменения батчены)
// Макротаска

4. Отладка Event Loop

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

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

  Promise.resolve()
    .then(() => {
      console.log('3. Микрозадача 1 (Promise)');
      
      queueMicrotask(() => {
        console.log('4. Микрозадача 2 (queueMicrotask)');
      });
    })
    .then(() => {
      console.log('5. Микрозадача 3 (Promise.then)');
    });

  queueMicrotask(() => {
    console.log('6. Микрозадача 4 (queueMicrotask)');
  });

  console.log('7. Конец синхронного кода');
}

logEventLoop();

// Результат:
// 1. Синхронный код
// 7. Конец синхронного кода
// 3. Микрозадача 1 (Promise)
// 6. Микрозадача 4 (queueMicrotask)
// 4. Микрозадача 2 (queueMicrotask)
// 5. Микрозадача 3 (Promise.then)
// 2. Макротаска (setTimeout)

5. Порядок выполнения с async/await

async function example() {
  console.log('1. Синхронный кар внутри async');

  await Promise.resolve();
  console.log('2. После await (микрозадача)');
}

console.log('3. До вызова async');
example();
console.log('4. После вызова async');

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

// Результат:
// 3. До вызова async
// 1. Синхронный код внутри async
// 4. После вызова async
// 2. После await (микрозадача)
// 5. Макротаска

6. Получение актуального значения из DOM

function updateDOM(element, data) {
  element.textContent = data;
  
  // Макротаска для следующей отрисовки
  setTimeout(() => {
    console.log('Браузер отрисовал изменения');
  }, 0);
  
  // Микрозадача перед отрисовкой
  queueMicrotask(() => {
    // В этот момент элемент НЕ отрисован, но текст изменился
    console.log('textContent:', element.textContent); // data
    console.log('offsetHeight:', element.offsetHeight); // старое значение
  });
}

Таблица: Макротаски vs Микротаски

ХарактеристикаМакротаскиМикротаски
ПримерыsetTimeout, setIntervalPromise, queueMicrotask
ПриоритетНижеВыше
Event LoopПосле микротасокПосле синхронного кода
РендерингПосле каждой макротаскиНет гарантий
ИспользованиеОтложить выполнениеБатчинг, реактивность
Цикл обновленияРаз в макротаскеНесколько раз в макротаске

Визуализация Event Loop

const visualizeEventLoop = () => {
  console.log('%c=== НАЧАЛО СОБЫТИЯ ===', 'color: blue');

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

  // Макротаска
  setTimeout(() => {
    console.log('%c=== НОВАЯ МАКРОТАСКА ===', 'color: red');
  }, 0);

  // Микротаска
  Promise.resolve().then(() => {
    console.log('%c=== МИКРОТАСКА ===', 'color: green');
  });

  console.log('Еще синхронный код');
  console.log('%c=== КОНЕЦ СОБЫТИЯ ===', 'color: blue');
};

visualizeEventLoop();

// Порядок вывода:
// === НАЧАЛО СОБЫТИЯ ===
// Синхронный код
// Еще синхронный код
// === КОНЕЦ СОБЫТИЯ ===
// === МИКРОТАСКА ===
// === НОВАЯ МАКРОТАСКА ===

Лучшие практики

  1. Используй queueMicrotask для небольших задач

    queueMicrotask(() => {
      // Выполнится сразу после текущего события
    });
    
  2. Батчируй обновления состояния

    queueMicrotask(() => {
      setState(1);
      setState(2);
      setState(3);
      // Один переполнение вместо трех
    });
    
  3. Используй Promise для асинхронного кода

    async function fetchData() {
      const data = await fetch('/api/data');
      // Выполнится в микротаске
    }
    
  4. Осторожно с вложенными микротасками

    queueMicrotask(() => {
      queueMicrotask(() => {
        // Это может привести к бесконечному циклу
      });
    });
    
  5. Помни о рендеринге после макротасок

    // Микротаски НЕ вызывают рендеринг
    queueMicrotask(() => {
      element.style.color = 'red'; // Не видно до следующей макротаски
    });
    

Выводы

  • Микрозадачи выполняются ДО макротасок
  • queueMicrotask создает микрозадачу явно
  • Promise автоматически создает микрозадачу
  • Используй для батчинга и реактивности
  • Понимание Event Loop критично для оптимизации
Как поставить микрозадачу в очередь? | PrepBro