await new Promise((resolve) => setTimeout(resolve, 0))setTimeout puts the rest of the loop in a new task. Between tasks, the browser can run event handlers and render a frame.await Promise.resolve() is the tempting wrong answer. It looks like a pause, but the code after it runs as a microtask. The browser runs every microtask before it handles input or paints. So await Promise.resolve(), await null and queueMicrotask all keep the main thread busy, and the long task stays one long task.