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

Как создать микротаск?

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

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

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

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

Как создать микротаск?

Микротаск (Microtask) — это единица асинхронной работы, которая выполняется после текущего синхронного кода, но перед макротаском (setTimeout, I/O). Микротаски критичны для понимания Event Loop в JavaScript. Все микротаски в очереди выполняются полностью перед тем, как браузер перейдёт к следующему макротаску или рендерингу.

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

1. Синхронный код
   |
   v
2. ВСЕ Микротаски (Promise, queueMicrotask, MutationObserver)
   |
   v
3. Рендеринг (requestAnimationFrame, браузер отрисовывает)
   |
   v
4. Один Макротаск (setTimeout, setInterval, I/O)
   |
   v
Обратно к пункту 2

Способы создать микротаск

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

Это наиболее используемый способ создания микротаска:

Promise.resolve()
  .then(() => {
    console.log('Это выполнится в микротаске');
  });

// Или с переданным значением
Promise.resolve(42)
  .then(value => {
    console.log('Значение:', value);
  });

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

Все эти микротаски выполнятся ПРЕЖДЕ чем будет выполнен any setTimeout.

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

Это встроенная функция для прямого создания микротаска:

queueMicrotask(() => {
  console.log('Явный микротаск');
});

// Эквивалентно (но явнее):
Promise.resolve().then(() => {
  console.log('Неявный микротаск через Promise');
});

// queueMicrotask изначально был более читаем:
queueMicrotask(() => {
  updateUI();
  calculateMetrics();
});

3. async/await (синтаксический сахар над Promise)

async function example() {
  console.log('Синхронный код');
  
  await Promise.resolve();
  
  console.log('Это выполнится в микротаске');
}

// Эквивалентно:
function example() {
  console.log('Синхронный код');
  
  return Promise.resolve()
    .then(() => {
      console.log('Это выполнится в микротаске');
    });
}

4. MutationObserver

Мутация DOM элемента создаёт микротаск:

const observer = new MutationObserver((mutations) => {
  console.log('DOM изменилась - микротаск!');
  mutations.forEach(mutation => {
    console.log('Изменен элемент:', mutation.target);
  });
});

// Наблюдаем за изменениями
observer.observe(document.body, {
  attributes: true,      // Отслеживать изменения атрибутов
  attributeOldValue: true,
  characterData: true,
  subtree: true
});

// Это вызывает микротаск
document.body.setAttribute('data-test', 'value');

5. Promise.prototype.then/catch/finally

fetch('/api/data')
  .then(response => response.json())
  .then(data => {
    console.log('Микротаск 1: данные получены');
  })
  .catch(error => {
    console.log('Микротаск: ошибка обработана');
  })
  .finally(() => {
    console.log('Микротаск: очистка');
  });

Практический пример: порядок выполнения

console.log('1. Начало');  // Синхронный код

setTimeout(() => {
  console.log('7. Макротаск 1 (setTimeout)');
  
  Promise.resolve().then(() => {
    console.log('8. Микротаск в макротаске');
  });
}, 0);

Promise.resolve()
  .then(() => {
    console.log('3. Микротаск 1 (Promise)');
    return Promise.resolve();
  })
  .then(() => {
    console.log('4. Микротаск 2 (Promise цепочка)');
  });

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

requestAnimationFrame(() => {
  console.log('6. RAF (перед макротаском)');
});

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

// Результат:
// 1. Начало
// 2. Конец синхронного кода
// 3. Микротаск 1 (Promise)
// 4. Микротаск 2 (Promise цепочка)
// 5. Микротаск 3 (queueMicrotask)
// 6. RAF (перед макротаском)
// 7. Макротаск 1 (setTimeout)
// 8. Микротаск в макротаске

Микротаск vs Макротаск

МикротаскМакротаск
Promise.then/catchsetTimeout
queueMicrotask()setInterval
async/awaitsetImmediate (Node.js)
MutationObserverrequestAnimationFrame
I/O операции

Ключевое различие: ВСЕ микротаски выполняются перед первым макротаском.

Практическое применение

Проблема: Race condition

// Неправильно: используется setTimeout
function updateData() {
  setTimeout(() => {
    data = newValue;
    render();
  }, 0);
}

// Правильно: используется микротаск
function updateData() {
  queueMicrotask(() => {
    data = newValue;
    render();
  });
}

Кэширование результатов

class DataFetcher {
  constructor() {
    this.cache = new Map();
    this.pendingMicrotasks = new Map();
  }
  
  async fetch(id) {
    if (this.cache.has(id)) {
      return this.cache.get(id);
    }
    
    // Все запросы за один Event Loop цикл будут батчированы
    if (!this.pendingMicrotasks.has(id)) {
      this.pendingMicrotasks.set(id, 
        queueMicrotask(() => this.batchFetch())
      );
    }
    
    return this.pendingMicrotasks.get(id);
  }
  
  batchFetch() {
    const ids = Array.from(this.pendingMicrotasks.keys());
    // Одна batch сеть операция для всех
  }
}

Отладка микротасков

// Chrome DevTools поддерживает отладку микротасков
// Нужно включить:
// 1. DevTools -> Settings -> Experiments
// 2. Поищи 'track async stack traces'
// 3. Включи опцию

function trackMicrotask(name) {
  console.time(name);
  queueMicrotask(() => {
    console.timeEnd(name);
  });
}

trackMicrotask('important-microtask');

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

  1. Используй queueMicrotask() когда нужен явный микротаск
  2. Избегай setTimeout(fn, 0) для чувствительных по времени операций
  3. Понимай Event Loop для предсказуемого поведения
  4. Документируй микротаски если они критичны
  5. Тестируй порядок выполнения особенно с async операциями
// Хорошо: явно и читаемо
queueMicrotask(() => {
  updateUI();
});

// Плохо: неясно что происходит
setTimeout(() => {
  updateUI();
}, 0);

Микротаски — это фундаментальная часть Event Loop JavaScript. Понимание их поведения критично для написания предсказуемого асинхронного кода.

Как создать микротаск? | PrepBro