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 — Раздел «Microtask queuing» — https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#microtask-queuing