Если у нас есть JS-код браузера, который выполняет много вычислений, как обеспечить надежный выход, чтобы позволить браузеру выполнять промежуточный рендеринг, например. мы хотим отображать индикаторы прогресса? Я попробовал обработчик ожидания requestAnimationFrame, но это — по крайней мере в Chromium — не всегда позволяет выполнить рендеринг.
Пример:
...compute...
statusElement.innerText = 'were almost done!';
await new Promise(resolve => window.requestAnimationFrame(resolve));
...compute...
часто, но не всегда показывает «были почти готовы», пока второе вычисление не завершится и код JS не завершится.
Итак, есть ли какой-нибудь канонический способ убедиться, что это изменение DOM видно, прежде чем продолжить?
@ivar он спрашивает о видимости изменений DOM
@underscore Да, я знаю. Если вы используете Web Worker и публикуете сообщения для обновления пользовательского интерфейса, то изменения DOM не являются проблемой.
Имеет ли это какое-либо отношение к принудительному перекомпоновке макета? Проверьте stackoverflow.com/questions/21664940/…
@James Принудительное перекомпоновывание не обязательно приводит к рендерингу/рисованию экрана



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Обратный вызов requestAnimationFrame вызывается непосредственно перед следующей перерисовкой. Именно со следующим вызовом requestAnimationFrame, который вы делаете после вызова этого обратного вызова, вы можете запланировать выполнение нового обратного вызова после этой перерисовки и перед следующей. Таким образом, только когда выполняется второй обратный вызов (переданный второму вызову requestAnimationFrame), перерисовка уже выполнена после ваших изменений.
Поэтому я бы посоветовал дождаться этого обещанного requestAnimationFrame в самом начале вашей функции обработки тяжелых данных, а затем сделать это снова позже.
Демо:
const beforePaint = () => new Promise(requestAnimationFrame);
const busyLoop = delay => {
const stop = performance.now() + delay;
while (performance.now() < stop) {
}
}
async function heavyTask(statusElement) {
await beforePaint(); // <-- add one of these at the very start
// Here the first paint since the call of `heavyTask` has not yet occurred.
statusElement.innerText = '0%';
await beforePaint(); // during the awaiting the first paint will happen
// Here we can be sure that "0%" has rendered
busyLoop(300);
statusElement.innerText = '30%';
await beforePaint();
busyLoop(100);
statusElement.innerText = '40%';
await beforePaint();
busyLoop(200);
statusElement.innerText = '60%';
await beforePaint();
busyLoop(300);
statusElement.innerText = '90%';
await beforePaint();
busyLoop(100);
statusElement.innerText = '100%';
}
heavyTask(document.querySelector("div"));<div></div>Если вы сначала удалите это await beforePaint();, вы можете не увидеть отображение «0%» по объясненной причине.
Понятно... Итак, чтобы гарантировать, что определенный reportProgress всегда будет работать независимо от того, где он вставлен, он должен ожидать двух кадров анимации - одного до и одного после изменения DOM?
Да, в этом вся суть. Но если вам нужно отрисовать последовательность фрагментов, вам не нужно удваивать ожидание кадров анимации. Вам просто нужно на один больше, чем может подсказать интуиция: и поместите его в самом начале.
Используете Web Worker?