Edited at

setTimeout()の0秒は4ミリ秒

window.setTimeout()がコールバックのネストにより連続的に呼ばれたとき、4ミリ秒の遅延が発生します。

たとえば以下のコードでは、countを1000にするのに4秒以上かかります。

let start = new Date().getTime();

let count = 0;
let next = () => {
count++;
if (count === 1000) {
console.log((new Date().getTime() - start) / 1000);
} else {
window.setTimeout(next, 0);
}
};
let id = window.setTimeout(next, 0);

MDNのページで言及されていました。「4ミリ秒」というのはHTML5で標準化された仕様です。

解決策も書かれており、window.postMessage()を使えばいいようです。

以下のコードを実行すると、countを1000にするのに0.070秒で済みます。

let start = new Date().getTime();

let count = 0;
let next = (e) => {
if (e.source !== window) return;
count++;
if (count === 1000) {
console.log((new Date().getTime() - start) / 1000);
} else {
window.postMessage("");
}
};
window.addEventListener("message", next, false);
window.postMessage("");

何らかの重い処理をしている間もユーザーの入力を受け付けたい、けど重い処理もできるだけ早く終わらせたい場合に、window.postMessage()の使用を検討するといいかもしれません。


補足

if (e.source !== window) return;は、メッセージの送信者が何者かを判定しています。この行を外してしまうと、クロスサイトスクリプティング攻撃を受けてしまう可能性が出てくるとのこと。MDNのページに明記されています。