Кратко
СкопированоБраузерное API, которое выполняет переданный код асинхронно.
Пример
СкопированоУбедимся, что переданная в queue
функция выполнится раньше, чем через set
. Для этого создадим страницу с формой, при отправке которой будут запускаться оба задания. Каждое из заданий будет печатать на экран уникальный текст:
<form class="compare-form" name="compare-form"> <h2> Вывод значений с помощью <code>queueMicrotask</code> и <code>setTimeout</code>: </h2> <p id="compare-output" class="compare-form__output" ></p> <button type="submit" class="button compare-form__submit-button" > Вывести текст </button> <button type="reset" class="button compare-form__reset-button" > Очистить содержимое </button></form>
<form class="compare-form" name="compare-form"> <h2> Вывод значений с помощью <code>queueMicrotask</code> и <code>setTimeout</code>: </h2> <p id="compare-output" class="compare-form__output" ></p> <button type="submit" class="button compare-form__submit-button" > Вывести текст </button> <button type="reset" class="button compare-form__reset-button" > Очистить содержимое </button> </form>
При отправке формы запустим задачи. Первой будет располагаться set
, а после — queue
.
const handleFormSubmit = (e) => { e.preventDefault() setTimeout(() => { output.innerText += 'Фраза добавлена из setTimeout()\n\n' }, 0) queueMicrotask(() => { output.innerText += 'Фраза добавлена из queueMicrotask()\n' })}
const handleFormSubmit = (e) => { e.preventDefault() setTimeout(() => { output.innerText += 'Фраза добавлена из setTimeout()\n\n' }, 0) queueMicrotask(() => { output.innerText += 'Фраза добавлена из queueMicrotask()\n' }) }
Вот и всё! Посмотрим, что у нас получилось:
В этом примере queue
принимает функцию, которая передаётся в очередь микрозадач, и возвращает undefined
.
queueMicrotask(() => { console.log('Хэй, я выполнюсь асинхронно!')})
queueMicrotask(() => { console.log('Хэй, я выполнюсь асинхронно!') })
Как понять
СкопированоОсновная причина использования queue
— обеспечение последовательности выполнения задач и снижение риска заметных пользователю задержек в операциях.
Представим ситуацию, когда получаем данные по указаному URL или когда запрос выполнялся ранее. Запрашиваем данные из кэша:
const output = document.querySelector('.logging-form__output')let data = []const cache = {}function getData(url) { if (url in cache) { data = cache[url] output.dispatchEvent(new Event('data-loaded')) } else { fetch(url) .then((response) => response.json()) .then(({ data }) => { cache[url] = data data = data output.dispatchEvent(new Event('data-loaded')) }) }}
const output = document.querySelector('.logging-form__output') let data = [] const cache = {} function getData(url) { if (url in cache) { data = cache[url] output.dispatchEvent(new Event('data-loaded')) } else { fetch(url) .then((response) => response.json()) .then(({ data }) => { cache[url] = data data = data output.dispatchEvent(new Event('data-loaded')) }) } }
Какую проблему тут можно заметить? В теле одного условия используется цепочка промисов, в другом — обычное синхронное выполнение. Из этого делаем вывод, что в разных условиях процесс выполнения будет отличаться.
Для наглядности навесим обработчик на событие submit
, в котором происходит вызов функции get
:
const form = document.querySelector('.logging-form')const handleFormSubmit = (e) => { e.preventDefault() output.innerText += 'Процесс загрузки данных…\n' getData('https://reqres.in/api/users/2') output.innerText += 'Процесс загрузки данных выполняется…\n'}form.addEventListener('submit', handleFormSubmit)
const form = document.querySelector('.logging-form') const handleFormSubmit = (e) => { e.preventDefault() output.innerText += 'Процесс загрузки данных…\n' getData('https://reqres.in/api/users/2') output.innerText += 'Процесс загрузки данных выполняется…\n' } form.addEventListener('submit', handleFormSubmit)
Не забудем про кастомное событие data
, инициируемое внутри функции get
. Навесим обработчик и на него:
const output = document.querySelector('.logging-form__output')const handleOutputDataLoaded = () => { output.innerText += 'Данные загружены\n'}output.addEventListener('data-loaded', handleOutputDataLoaded)
const output = document.querySelector('.logging-form__output') const handleOutputDataLoaded = () => { output.innerText += 'Данные загружены\n' } output.addEventListener('data-loaded', handleOutputDataLoaded)
Посмотрим, к каким результатам это приведёт. Для этого нажмите в демке на кнопку получения данных два раза.
Можете заметить недочёт после второго нажатия, когда данные берутся из кэша. Строка «Процесс загрузки данных выполняется…» выводится после «Данные загружены». Причём, когда данные приходили впервые, вывод строк был иным. Это происходит из-за того, что событие data
отправляется из асинхронного кода при первом чтении, а в случае чтения из кэша — из синхронного.
Исправим проблему и обернём тело первого условного блока в queue
. Таким образом, сделаем чтение данных из кэша асинхронной операцией:
if (url in cache) { queueMicrotask(() => { data = cache[url] textarea.dispatchEvent(new Event('data-loaded')) })}
if (url in cache) { queueMicrotask(() => { data = cache[url] textarea.dispatchEvent(new Event('data-loaded')) }) }
Посмотрим на итоговое решение после корректировки:
Отлично! Теперь процесс выполнения работает как при получении данных с сервера, так и при вытаскивании их из кэша.
В этом примере код схож со сценарием использования set
.
queueMicrotask(() => { console.log('Хэй, я выполнюсь асинхронно!')})
queueMicrotask(() => { console.log('Хэй, я выполнюсь асинхронно!') })
Оба сценария выполнят код асинхронно:
setTimeout(() => { console.log('Хэй, я выполнюсь асинхронно благодаря setTimeout')}, 0)
setTimeout(() => { console.log('Хэй, я выполнюсь асинхронно благодаря setTimeout') }, 0)
Так в чём же разница между обоими сценариями?
queue
добавляет переданную функцию в очередь микрозадач. Функции в этой очереди выполняются одна за другой (FIFO: First in First Out). Это значит, что когда текущая функция выполнилась, запускается следующая функция в очереди.
Все микрозадачи в очереди выполнятся только после того как текущий стек вызовов окажется пустым, но перед выполнением следующей макрозадачи.
Вернёмся к сравнению с set
. Передаваемые в set
функции относятся к макрозадачам. Каждая задача будет взята из очереди после того как управление передано циклу событий. Так что, если вызвать queue
после set
или наоборот, переданная в queue
функция начнёт исполняться первой.
Подробнее про микро- и макрозадачи
JavaScript имеет в своём арсенале различные виды очередей, а также стек вызовов. Чтобы разобраться с процессом работы, давайте рассмотрим необходимый минимум:
- стек вызовов — контейнер для выполнения синхронных операций;
- очередь микрозадач — контейнер для хранения асинхронных операций с высоким приоритетом;
- очередь макрозадач — контейнер для хранения асинхронных операций с низким приоритетом.
Рассмотрим, как работают элементы процесса:
- Первый, кто начинает процесс выполнения, — стек вызовов.
- После того, как JavaScript убеждается, что стек пуст, в него добавляются задачи из очереди микрозадач.
- Если в процессе выполнения одной микрозадачи в очередь добавится другая, обе задачи выполнятся в этот же проход. Процесс выполнения продолжается, пока очередь не опустеет. Как только это произойдёт — выполняется одна задача из очереди макрозадач.
- Сразу после макрозадачи в стек добавятся микрозадачи. Это произойдёт перед выполнением следующей макрозадачи, отображением изменений на странице или другими действиями.
На практике
Скопированосоветует Скопировано
🛠 queue
— полезная вещь, когда откладываете запуск задачи на ближайшее время. Не забудьте, что выполнение больших объёмов работы на стороне микрозадач — проблемная точка для интерактивности приложения. Подходите к выбору с умом. Возможно, для решения вашей проблемы лучше рассмотреть set
или request
.
🛠 queue
влияет на процесс работы очереди микрозадач. Вызываемая функция, которая становится микрозадачей, создаёт ряд других микрозадач (например, благодаря циклу). Случайно созданная рекурсия приведёт к полному прекращению работы приложения.