HTML | Очередь микрозадач

HTML | Очередь микрозадач

Для веб-разработчиков (не нормативно)

self.queueMicrotask(callback)

Помещает в очередь микрозадачу для выполнения данного обратного вызова callback.

 

Метод queueMicrotask(callback) должен поставить микрозадачу в очередь для вызова обратного вызова callback, и если обратный вызов callback вызывает исключение, сообщить об исключении.

Метод queueMicrotask() позволяет авторам планировать обратный вызов в очереди микрозадач. Это позволяет их коду запускаться, как только стек контекста выполнения JavaScript становится пустым, что происходит, когда все выполняемые в данный момент синхронные сценарии JavaScript завершаются. Это не возвращает управление циклу обработки событий, как в случае использования, например, setTimeout(f, 0).

Авторы должны знать, что планирование большого количества микрозадач имеет те же недостатки производительности, что и выполнение большого количества синхронного кода. Оба предотвратят выполнение браузером своей собственной работы, такой как рендеринг (визуализация). Во многих случаях лучше использовать requestAnimationFrame() или requestIdleCallback(). В частности, если цель состоит в том, чтобы запустить код перед следующим циклом рендеринга, это и есть цель requestAnimationFrame().

Как видно из следующих примеров, функцию queueMicrotask() лучше всего рассматривать как механизм переупорядочения синхронного кода, эффективно помещающий поставленный в очередь код сразу после завершения выполняемого в данный момент синхронного JavaScript.

 

Пример

Наиболее распространённой причиной использования queueMicrotask() является создание согласованного порядка даже в тех случаях, когда информация доступна синхронно, без чрезмерной задержки.

Например, рассмотрим пользовательский элемент, запускающий событие загрузки load, которое также поддерживает внутренний кеш ранее загруженных данных. Наивная реализация может выглядеть так:

MyElement.prototype.loadData = function (url) {
  if (this._cache[url]) {
    this._setData(this._cache[url]);
    this.dispatchEvent(new Event("load"));
  } else {
    fetch(url).then(res => res.arrayBuffer()).then(data => {
      this._cache[url] = data;
      this._setData(data);
      this.dispatchEvent(new Event("load"));
    });
  }
};

Однако эта наивная реализация проблематична, поскольку заставляет пользователей испытывать непоследовательное поведение. Например, такой код, как

element.addEventListener("load", () => console.log("loaded"));
console.log("1");
element.loadData();
console.log("2");

иногда будет регистрировать «1, 2, загружено» (если данные необходимо получить), а иногда регистрировать «1, загружено, 2» (если данные уже кэшированы). Точно так же после вызова loadData() будет несовместимо, установлены ли данные для элемента.

Чтобы получить согласованный порядок, можно использовать queueMicrotask():

MyElement.prototype.loadData = function (url) {
  if (this._cache[url]) {
    queueMicrotask(() => {
      this._setData(this._cache[url]);
      this.dispatchEvent(new Event("load"));
    });
  } else {
    fetch(url).then(res => res.arrayBuffer()).then(data => {
      this._cache[url] = data;
      this._setData(data);
      this.dispatchEvent(new Event("load"));
    });
  }
};

Существенно перестраивая код в очереди так, чтобы он находился после опустошения стека контекста выполнения JavaScript, это обеспечивает согласованное упорядочение и обновление состояния элемента.

 

Пример

Другое интересное использование queueMicrotask() состоит в том, чтобы разрешить нескоординированную «пакетную» работу нескольких вызывающих объектов. Например, рассмотрим библиотечную функцию, которая хочет отправить данные куда-то как можно скорее, но не хочет выполнять несколько сетевых запросов, если этого легко избежать. Один из способов сбалансировать это будет выглядеть так:

const queuedToSend = [];

function sendData(data) {
  queuedToSend.push(data);

  if (queuedToSend.length === 1) {
    queueMicrotask(() => {
      const stringToSend = JSON.stringify(queuedToSend);
      queuedToSend.length = 0;

      fetch("/endpoint", stringToSend);
    });
  }
}

В этой архитектуре несколько последующих вызовов sendData() в выполняющемся в данный момент синхронном JavaScript будут объединены в один вызов fetch(), но без промежуточных задач цикла событий, вытесняющих выборку (как это произошло бы с аналогичным кодом, который вместо этого использовал setTimeout()).

 

Информационные ссылки

HTML | Оглавление

Стандарт HTML — Раздел «Microtask queuing» — https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#microtask-queuing