なぜsetTimeout(fn、0)が時々役立つのですか?


872

最近、かなり厄介なバグに遭遇しました。コードが<select>JavaScriptを介して動的にロードされていました。この動的にロード<select>された値は事前に選択されていました。IE6では、我々はすでに選択を修正するためのコードを持っていた<option>、時にはので、<select>selectedIndex値は、選択と同期してだろう<option>s 'はindex以下のように属性:

field.selectedIndex = element.index;

ただし、このコードは機能していませんでした。フィールドselectedIndexが正しく設定されていたとしても、間違ったインデックスが選択されることになります。ただし、alert()適切なタイミングでステートメントを挿入した場合、正しいオプションが選択されます。これはある種のタイミングの問題かもしれないと思い、以前コードで見たランダムなものを試しました:

var wrapFn = (function() {
    var myField = field;
    var myElement = element;

    return function() {
        myField.selectedIndex = myElement.index;
    }
})();
setTimeout(wrapFn, 0);

そしてこれはうまくいきました!

私には問題の解決策がありますが、なぜこれが問題を解決するのか正確にわからないので不安です。正式な説明はありますか?を使用して「後で」関数を呼び出すことで、どのブラウザの問題を回避できsetTimeout()ますか?


2
質問のほとんどは、なぜそれが役立つのかを説明しています。なぜこれが起こるのかを知る必要がある場合-私の答えを読んでください:stackoverflow.com/a/23747597/1090562
Salvador Dali

18
フィリップロバーツはこれを彼の講演「イベントループとは一体何ですか?」で可能な限り最良の方法で説明しています。youtube.com/watch?v=8aGhZQkoFbQ
vasa

あなたが急いでいる場合、これは彼が質問に正確に対処し始めるビデオの一部です:youtu.be/8aGhZQkoFbQ?t=14m54s。無事、ビデオ全体は確かに一見の価値があります。
ウィリアムハンプシャー

4
setTimeout(fn)setTimeout(fn, 0)btw と同じです。
vsync 2018

回答:


827

質問では、次の間に競合状態がありました。

  1. ブラウザーがドロップダウンリストを初期化し、選択したインデックスを更新する準備ができている。
  2. 選択したインデックスを設定するためのコード

あなたのコードは一貫してこの競争に勝ち、ブラウザの準備が整う前にドロップダウン選択を設定しようとしました。これはバグが発生することを意味します。

この競合は、JavaScriptがページレンダリングと共有される単一の実行スレッドを持っているために存在しました。実際、JavaScriptを実行すると、DOMの更新がブロックされます。

あなたの回避策は:

setTimeout(callback, 0)

呼び出す setTimeoutコールバックで、第二引数としてゼロが実行されるコールバックをスケジュールします非同期的に可能な最短の遅延の後、 -タブにフォーカスがあると実行のJavaScriptのスレッドがビジーでないときの周りに10msとなります。

したがって、OPのソリューションは、選択されたインデックスの設定である約10msだけ遅延することでした。これにより、ブラウザにDOMを初期化する機会が与えられ、バグが修正されました。

Internet Explorerのすべてのバージョンは風変わりな動作を示し、この種の回避策は時々必要でした。あるいは、OPのコードベースの本物のバグであった可能性もあります。


フィリップロバーツの講演「イベントループとは一体何ですか?」を参照してくださいより完全な説明のために。


276
「解決策は、レンダリングのスレッドが追いつくようにJavaScriptの実行を「一時停止」することです。」完全に真ではありません。setTimeoutが行うのは、ブラウザーイベントキューに新しいイベントを追加することであり、レンダリングエンジンは既にそのキューにあります(完全には真ではありませんが、十分に近いため)、setTimeoutイベントの前に実行されます。
デビッドモルダー

48
はい、それははるかに詳細ではるかに正しい答えです。しかし、私のトリックは、そのトリックが機能する理由を人々が理解するのに「十分に正しい」ものです。
staticsan 2013年

2
@DavidMulder、それはブラウザがCSSを解析し、JavaScript実行スレッドとは異なるスレッドでレンダリングすることを意味しますか?
ジェイソン2013年

8
いいえ、それらは原則として同じスレッドで解析されます。そうでない場合、数行のDOM操作が常にリフローをトリガーし、実行速度に非常に悪い影響を与えます。
David Mulder

30
このビデオは、なぜ私たちがsetTimeout 02014.jsconf.eu/speakers/…
davibq

665

序文:

他のいくつかの答えは正しいですが、実際に解決されている問題が何であるかを説明していないため、この詳細な説明を提示するためにこの答えを作成しました。

そのため、私はブラウザが何をするか、そしてどのように使用setTimeout()することが役立つかの詳細なウォークスルーを投稿しています。見た目は長めですが、実際には非常にシンプルで簡単です-非常に詳細にしただけです。

更新:以下の説明を実演するためにJSFiddleを作成しました:http ://jsfiddle.net/C2YBE/31/ 。多くのおかげで、それをキックスタートを支援するための@ThangChungへ。

UPDATE2: JSFiddleのWebサイトが停止したり、コードが削除されたりした場合に備えて、この回答の最後にコードを追加しました。


詳細

「何かをする」ボタンと結果divを備えたWebアプリを想像してみてください。

onClick「何かを行う」ボタンのハンドラーは、関数「LongCalc()」を呼び出し、2つのことを行います。

  1. 非常に長い計算を行います(たとえば3分かかります)

  2. 計算結果を結果divに出力します。

ユーザーがこれをテストし始め、「何かを行う」ボタンをクリックすると、ページはそこに座って3分間何もしないように見え、落ち着きがなくなり、もう一度ボタンをクリックし、1分待って、何も起こらず、もう一度ボタンをクリックします...

問題は明白です-何が起こっているかを示す「ステータス」DIVが必要です。それがどのように機能するか見てみましょう。


したがって、「ステータス」DIV(最初は空)を追加し、onclickハンドラー(関数LongCalc())を変更して4つのことを行います。

  1. ステータス「計算中... 3分ほどかかる場合があります」をステータスDIVに入力します

  2. 非常に長い計算を行います(たとえば3分かかります)

  3. 計算結果を結果divに出力します。

  4. ステータス「計算完了」をステータスDIVに入力します

また、アプリをユーザーに喜んで提供して、再テストしてもらいます。

彼らは非常に怒ってあなたに戻ってきます。また、ボタンをクリックしたときに、ステータスDIVが「計算中...」のステータスで更新されなかったことを説明します


頭をかき、StackOverflowで質問して(またはドキュメントやgoogleを読んで)、問題を認識します。

ブラウザーは、イベントに起因するすべての「TODO」タスク(UIタスクとJavaScriptコマンドの両方)を単一のキューに配置します。残念ながら、新しい「計算中...」の値で「ステータス」DIVを再描画すると、キューの最後に移動する別のTODOになります。

ユーザーのテスト中のイベントの内訳と、各イベント後のキューの内容は次のとおりです。

  • キュー: [Empty]
  • イベント:ボタンをクリックします。イベント後のキュー:[Execute OnClick handler(lines 1-4)]
  • イベント:OnClickハンドラーの最初の行を実行します(ステータスDIV値の変更など)。イベント後のキュー:[Execute OnClick handler(lines 2-4), re-draw Status DIV with new "Calculating" value]DOMの変更は瞬時に行われますが、対応するDOM要素を再描画するには、DOMの変更によってトリガーされ、キューの最後に移動した新しいイベントが必要です。
  • 問題!!! 問題!!!詳細は以下で説明します。
  • イベント:ハンドラー(計算)で2行目を実行します。後のキュー:[Execute OnClick handler(lines 3-4), re-draw Status DIV with "Calculating" value]
  • イベント:ハンドラーで3行目を実行します(結果DIVに入力)。後のキュー:[Execute OnClick handler(line 4), re-draw Status DIV with "Calculating" value, re-draw result DIV with result]
  • イベント:ハンドラーで4行目を実行(ステータスDIVに "DONE"を入力)。キュー:[Execute OnClick handler, re-draw Status DIV with "Calculating" value, re-draw result DIV with result; re-draw Status DIV with "DONE" value]
  • イベント:ハンドラサブreturnから暗黙的に実行されonclickます。「Execute OnClickハンドラー」をキューから取り出し、キューの次の項目の実行を開始します。
  • 注:既に計算が完了しているため、ユーザーには3分がすでに経過しています。再描画イベントはまだ発生していません!!!
  • イベント:ステータスDIVを「計算中」の値で再描画します。再描画を行い、それをキューから削除します。
  • イベント:結果値で結果DIVを再描画します。再描画を行い、それをキューから削除します。
  • イベント:「完了」の値でステータスDIVを再描画します。再描画を行い、それをキューから削除します。鋭い目で見る人は、「計算中」の値を持つステータスDIVがマイクロ秒の端数で点滅することにさえ気付くかもしれません- 計算が終了した後

つまり、根本的な問題は、「ステータス」DIVの再描画イベントが最後にキューに配置されることです。3分かかる「実行ライン2」イベントの後、実際の再描画は、計算が行われた後。


救助に来るsetTimeout()。それはどのように役立ちますか?を介して長時間実行コードを呼び出すことによりsetTimeout、実際には2つのイベントが作成されます。setTimeout実行自体と、(タイムアウトが0であるため)、実行されるコードの個別のキューエントリです。

したがって、問題を解決するには、onClickハンドラーを2つのステートメント(新しい関数または内のブロックのみonClick)に変更します。

  1. ステータス「計算中... 3分ほどかかる場合があります」をステータスDIVに入力します

  2. setTimeout()タイムアウト0で実行し、LongCalc()関数を呼び出します

    LongCalc()機能は前回とほぼ同じですが、最初のステップとして明らかに「計算中...」ステータスDIV更新がありません。代わりにすぐに計算を開始します。

では、イベントシーケンスとキューは現在どのように見えますか?

  • キュー: [Empty]
  • イベント:ボタンをクリックします。イベント後のキュー:[Execute OnClick handler(status update, setTimeout() call)]
  • イベント:OnClickハンドラーの最初の行を実行します(ステータスDIV値の変更など)。イベント後のキュー:[Execute OnClick handler(which is a setTimeout call), re-draw Status DIV with new "Calculating" value]
  • イベント:ハンドラーの2行目を実行(setTimeout呼び出し)。後のキュー:[re-draw Status DIV with "Calculating" value]。キューには、あと0秒間新しいものはありません。
  • イベント:タイムアウトからのアラームが0秒後に鳴ります。後のキュー:[re-draw Status DIV with "Calculating" value, execute LongCalc (lines 1-3)]
  • イベント:「計算中」の値でステータスDIVを再描画します。後のキュー:[execute LongCalc (lines 1-3)]。この再描画イベントは、アラームが鳴る前に実際に発生する可能性があることに注意してください。これは同様に機能します。
  • ...

やったー!計算が始まる前に、ステータスDIVが「計算中...」に更新されました!!!



以下は、これらの例を示すJSFiddleのサンプルコードです:http : //jsfiddle.net/C2YBE/31/ :

HTMLコード:

<table border=1>
    <tr><td><button id='do'>Do long calc - bad status!</button></td>
        <td><div id='status'>Not Calculating yet.</div></td>
    </tr>
    <tr><td><button id='do_ok'>Do long calc - good status!</button></td>
        <td><div id='status_ok'>Not Calculating yet.</div></td>
    </tr>
</table>

JavaScriptコード:(onDomReadyjQuery 1.9で実行され、必要になる場合があります)

function long_running(status_div) {

    var result = 0;
    // Use 1000/700/300 limits in Chrome, 
    //    300/100/100 in IE8, 
    //    1000/500/200 in FireFox
    // I have no idea why identical runtimes fail on diff browsers.
    for (var i = 0; i < 1000; i++) {
        for (var j = 0; j < 700; j++) {
            for (var k = 0; k < 300; k++) {
                result = result + i + j + k;
            }
        }
    }
    $(status_div).text('calculation done');
}

// Assign events to buttons
$('#do').on('click', function () {
    $('#status').text('calculating....');
    long_running('#status');
});

$('#do_ok').on('click', function () {
    $('#status_ok').text('calculating....');
    // This works on IE8. Works in Chrome
    // Does NOT work in FireFox 25 with timeout =0 or =1
    // DOES work in FF if you change timeout from 0 to 500
    window.setTimeout(function (){ long_running('#status_ok') }, 0);
});

12
素晴らしい答えDVK!ここにあなたの例を説明する要点がありますgist.github.com/kumikoda/5552511#file-timeout-html
kumikoda '10

4
本当にクールな答え、DVK。想像しやすいように、そのコードをjsfiddle jsfiddle.net/thangchung/LVAaV
thangchung

4
@ThangChung-JSFiddleでより良いバージョン(2つのボタン、各ケースに1つ)を作成しようとしました。ChromeとIEではデモとして機能しますが、何らかの理由でFFでは機能しません。jsfiddle.net/ C2YBE / 31を参照してください。FFはここで働いていない理由を私は尋ねた:stackoverflow.com/questions/20747591/...
DVK

1
@DVK "ブラウザは、イベントの結果であるすべての" TODO "タスク(UIタスクとJavaScriptコマンドの両方)を単一のキューに配置します。" これのソースを提供していただけますか?異なるUI(レンダリングエンジン)とJSスレッドを持っているはずの
Imho arentブラウザー

1
@bhavya_wいいえ、すべてが1つのスレッドで発生します。これが、長いjs計算がUIをブロックする可能性がある理由です
Seba Kerckhof、

88

John Resigの「JavaScriptタイマーの仕組み」に関する記事をご覧ください。タイムアウトを設定すると、エンジンが現在の呼び出しスタックを実行するまで、実際には非同期コードがキューに入れられます。


23

setTimeout() が0に設定されている場合でも、DOM要素がロードされるまでしばらく時間がかかります。

これをチェックしてください:setTimeout


23

ほとんどのブラウザには、メインスレッドと呼ばれるプロセスがあり、JavaScriptタスクの実行、UIの更新(ペイント、再描画、リフローなど)を実行します。

一部のJavaScript実行タスクとUI更新タスクは、ブラウザーのメッセージキューに入れられてから、ブラウザーのメインスレッドにディスパッチされて実行されます。

メインスレッドがビジーなときにUI更新が生成されると、タスクはメッセージキューに追加されます。

setTimeout(fn, 0);これfnを実行するキューの最後に追加します。一定の時間が経過すると、メッセージキューにタスクが追加されるようにスケジュールされます。


「すべてのJavaScript実行およびUI更新タスクがブラウザーイベントキューシステムに追加され、それらのタスクはブラウザーのメインUIスレッドにディスパッチされて実行されます。
bhavya_w 14

4
高パフォーマンスJavaScript(ニコラスザカス、ストヤンステファノフ、ロスハーメス、ジュリアンルコント、マットスウィーニー)
アーリー

このへの反対票add this fn to the end of the queue。最も重要なのは、setTimeoutこの関数、このループサイクルの終わり、または次のループサイクルの始まりを正確に追加する場所です。
グリーン

19

ここには賛成投票の矛盾があり、証明がなければ誰を信じるかを知る方法はありません。@DVKが正しく、@ SalvadorDaliが正しくないことの証明は次のとおりです。後者の主張:

「そして、これが理由です。0ミリ秒の遅延時間でsetTimeoutを設定することはできません。最小値はブラウザによって決定され、0ミリ秒ではありません。歴史的に、ブラウザはこの最小値を10ミリ秒に設定しますが、HTML5仕様と最近のブラウザでは4ミリ秒に設定されています。」

4msの最小タイムアウトは、何が起こっているかには関係ありません。実際に起こることは、setTimeoutがコールバック関数を実行キューの最後にプッシュすることです。setTimeout(callback、0)の後、実行に数秒かかるブロッキングコードがある場合、ブロッキングコードが完了するまで、コールバックは数秒間実行されません。このコードを試してください:

function testSettimeout0 () {
    var startTime = new Date().getTime()
    console.log('setting timeout 0 callback at ' +sinceStart())
    setTimeout(function(){
        console.log('in timeout callback at ' +sinceStart())
    }, 0)
    console.log('starting blocking loop at ' +sinceStart())
    while (sinceStart() < 3000) {
        continue
    }
    console.log('blocking loop ended at ' +sinceStart())
    return // functions below
    function sinceStart () {
        return new Date().getTime() - startTime
    } // sinceStart
} // testSettimeout0

出力は次のとおりです。

setting timeout 0 callback at 0
starting blocking loop at 5
blocking loop ended at 3000
in timeout callback at 3033

あなたの答えは何かを証明するものではありません。特定の状況下にあるマシンで、コンピューターがいくつかの数値をスローすることを示しています。関連することを証明するには、数行のコードと数行よりも少し多くのものが必要です。
サルバドールダリ

6
@SalvadorDali、私の証明はほとんどの人が理解できるほど明確だと思います。あなたは防御的だと感じており、それを理解する努力をしていないと思います。明確にさせていただきますが、何を理解していないのかわかりません。私の結果が疑われる場合は、自分のマシンでコードを実行してみてください。
Vladimir Kornea 2014年

最後にはっきりさせておきます。私の答えは、タイムアウト、間隔の興味深いプロパティのいくつかを明確にすることであり、有効で認識可能なソースによって十分にサポートされています。あなたはそれが間違っていると強く主張し、何らかの理由で証拠と名付けた自分のコードを指摘しました。コードに問題はありません(私はそれに反対しません)。私は私の答えが間違っていないと言っています。いくつかのポイントを明らかにします。ポイントが見えないので、この戦争をやめようと思います。
サルバドールダリ

15

これを行う1つの理由は、コードの実行を別の後続のイベントループに延期することです。ある種のブラウザイベント(マウスクリックなど)に応答する場合、現在のイベントが処理された後でのみ操作を実行する必要がある場合があります。setTimeout()施設には、それを行うための最も簡単な方法です。

2015年ですので、今編集してください。もあることに注意してくださいrequestAnimationFrame()。これはまったく同じではありませんが、setTimeout(fn, 0)言及する価値があるほど十分に近いものです。


これはまさに私がそれが使用されているのを見た場所の一つです。=)
ザイボット、2011年

参考

2
コードの実行を別の後続のイベントループに延期することです。後続のイベントループをどのように理解しますか?現在のイベントループとは何ですか。現在のイベントループターンで、どのようにして現在を知っていますか?
グリーン(

@Greenまあ、そうではありません。JavaScriptランタイムの状況を直接確認することはできません。
2015年

requestAnimationFrameは、IEとFirefoxでUIが時々更新されないという問題を解決しました
David

9

これは古い質問と古い回答です。私はこの問題に新しい見方を追加し、なぜこれが起こるのか、なぜこれが有用なのかではなく答えたいと思いました。

したがって、2つの機能があります。

var f1 = function () {    
   setTimeout(function(){
      console.log("f1", "First function call...");
   }, 0);
};

var f2 = function () {
    console.log("f2", "Second call...");
};

次に、次の順序でそれらを呼び出しf1(); f2();て、2番目のものが最初に実行されたことを確認します。

そしてここに理由があります:setTimeout0ミリ秒の時間遅延を持つことは不可能です。最小値は、ブラウザによって決定され、それが0ミリ秒ではありません。歴史的にブラウザはこの最小値を10ミリ秒に設定しますが、HTML5仕様と最新のブラウザでは4ミリ秒に設定されています。

ネストレベルが5より大きく、タイムアウトが4より小さい場合は、タイムアウトを4に増やします。

また、mozillaから:

最新のブラウザーで0ミリ秒のタイムアウトを実装するには、ここで説明するようにwindow.postMessage()を使用できます。

PS情報は、次の記事を読んだ後に取得されます。


1
@ user2407309冗談ですか?HTML5仕様が間違っていて正しいということですか?反対票を投じる前に出典を読み、強い主張をしてください。私の答えは、HTML仕様と歴史的記録に基づいています。同じことを何度も何度も説明する答えを出す代わりに、以前の答えにはなかった新しいものを追加しました。これが唯一の理由だと言っているのではなく、単に何か新しいものを見せているだけです。
サルバドールダリ

1
これは正しくありません:「そして、ここに理由があります:0ミリ秒の遅延時間でsetTimeoutを持つことは不可能です。」それは理由ではありません。4msの遅延は、なぜsetTimeout(fn,0)が役立つのかとは無関係です。
Vladimir Kornea 2014年

@ user2407309簡単に変更して、「他の人が述べた理由に追加することはできません...」に変更できます。そのため、特にあなた自身の回答が新しいことを何も伝えていない場合は、このために反対投票するのはばかげています。ほんの少しの編集で十分です。
サルバドールダリ

10
サルバドールダリ:ここでのマイクロフレイム戦争の感情的な側面を無視する場合、@ VladimirKorneaが正しいことを認めざるを得ないでしょう。ブラウザーが0ミリ秒の遅延を4ミリ秒にマップすることは事実ですが、そうでなくても、結果は同じです。ここでの駆動メカニズムは、コードが呼び出しスタックではなくキューにプッシュされることです。この優れたJSConfプレゼンテーションをご覧ください。問題を明確にするのに役立ちます:youtube.com/watch
v

1
認定された最小4ミリ秒の見積もりがグローバル最小4ミリ秒であると考える理由について、私は混乱しています。HTML5仕様のショーからあなたの引用として、最小値は、あなたがにネストされた呼び出してきた場合にのみ、4ミリ秒ですsetTimeout/ setInterval深いつ以上のレベルを。そうでない場合、最小値は0ミリ秒です(タイムマシンが不足している場合)。Mozillaのドキュメントでは、ネストされたケースだけでなく、繰り返しのケースもカバーするように拡張されています(setInterval間隔が0の場合、すぐに数回再スケジュールされ、その後遅延が長くなりsetTimeoutます)。
ShadowRanger

8

期間が渡されているので0、に渡されたコードsetTimeoutを実行フローから削除するためだと思います。したがって、時間がかかる可能性がある関数の場合、後続のコードの実行が妨げられることはありません。


参考

7

これら2つのトップ評価の答えはどちらも間違っています。同時実行モデルとイベントループに関するMDNの説明を確認してください。何が起こっているのかが明らかになるはずです(そのMDNリソースは本物の宝石です)。そして単に使用する setTimeoutすることは、この小さな問題を「解決する」ことに加えて、コードに予期しない問題を追加可能性があります。

実際にここに行くことに基づいて、または何か「ブラウザはかなり準備がまだ同時実行、ためではないかもしれない」ということではありません「各行キューの後ろに追加されたイベントです」。

jsfiddle DVKによって提供さは確かに問題を示したが、それのための彼の説明は正しくありません。

彼のコードで起こっていることは、彼が最初にイベントハンドラーを click#doボタンのイベントにです。

次に、実際にボタンをクリックすると、messageに追加されるイベントハンドラー関数を参照するが作成されますmessage queue。ときevent loop、このメッセージに達すると、それが作成されますframejsfiddleでのクリックイベントハンドラに関数呼び出しで、スタック上。

そして、これは興味深いところです。私たちはJavascriptを非同期であると考えるのに慣れているので、この小さな事実を見落としがちです。次のフレームを実行する前に、すべてのフレームを完全に実行する必要があります。並行性はありません、人々。

これは何を意味するのでしょうか?つまり、関数がメッセージキューから呼び出されると、生成されたスタックが空になるまでキューをブロックします。または、より一般的に言えば、関数が戻るまでブロックします。そしてそれはブロックしますすべてをます、DOMレンダリング操作、スクロールなど、を。確認が必要な場合は、フィドルで長時間実行する操作の期間を長くしてみてください(たとえば、外側のループをさらに10回実行します)。実行中はページをスクロールできません。実行時間が長すぎると、ページが応答しなくなるため、ブラウザはプロセスを強制終了するかどうかを尋ねてきます。フレームが実行されており、イベントループとメッセージキューは、完了するまでスタックしています。

では、なぜこのテキストの副作用が更新されないのでしょうか?DOM内の要素の値を変更しいる間— console.log()変更後すぐにその値を確認し、変更されいることを確認できます(これは、DVKの説明が正しくない理由を示しています)—ブラウザーはスタックが空になるのを待機しています(on返すハンドラー関数)とメッセージが終了するため、最終的には、変異操作に対する反応としてランタイムによって追加されたメッセージを実行し、UIにその変異を反映することができます。 。

これは、コードの実行が完了するのを実際に待っているためです。イベントベースの非同期Javascriptで通常行うように、「誰かがこれをフェッチして、結果を返してこの関数を呼び出して、ありがとう、そして今や、すぐに何でもできるようになりました」とは言いませんでした。我々は、我々は、同じDOM要素を更新し、そして、戻って、他の機能は、長い時間のために働く、別の関数を呼び出すと、私たちはDOM要素を更新し、クリックイベントハンドラ関数を入力し、その後効果的に空に、我々は最初の関数から戻りますスタック。そしてその後、ブラウザは非常によく、「上-DOM-変異」タイプのイベントいくつかの内部をトリガすることによって、私たちによって生成されたメッセージであるかもしれないキュー内の次のメッセージに得ることができます。

ブラウザUIは、現在実行中のフレームが完了する(関数が返される)まで、UIを更新できません(または更新しないことを選択します)。個人的には、これは制限というより設計によるものだと思います。

なぜそれが機能するのsetTimeoutですか?これは、長期実行関数の呼び出しを独自のフレームから効果的に削除し、windowコンテキスト内で後で実行されるようにスケジュールし、それ自体がすぐに戻ってメッセージキューが他のメッセージを処理できるようにするためです。そして、アイデアは、DOMのテキストを変更したときにJavascriptでトリガーされたUI「on update」メッセージが、長時間実行関数のキューに入れられたメッセージよりも前になり、ブロックする前にUI更新が発生するというものです。長い間。

a)長時間実行関数実行時にすべてをブロックすることに注意してください。b)UIの更新が実際にメッセージキューの前にあることは保証されていません。私の2018年6月のChromeブラウザーでは、の値は0フィドルが示す問題を「修正」しません— 10は修正します。実行時間の長い関数を「後で」実行するようにスケジュールする前にトリガーが実行されるため、UI更新メッセージがその前にキューに入れられるべきだと私には論理的に思われるので、私は実際にこれに少し困惑しています。しかし、おそらくV8エンジンに干渉する可能性のあるいくつかの最適化があるか、多分私の理解が不十分です。

では、を使用する上での問題はsetTimeout何でしょうか、そしてこの特定のケースのより良い解決策は何ですか?

まず、setTimeoutこのようなイベントハンドラーで別の問題を緩和しようとする場合の問題は、他のコードを混乱させる傾向があります。これが私の仕事の実際の例です:

同僚は、イベントループについて誤った情報を理解しているsetTimeout 0ため、レンダリングにテンプレートレンダリングコードを使用してJavascriptを「スレッド化」しようとしました。彼はもう尋ねる必要はありませんが、おそらく彼はタイマーを挿入してレンダリング速度(関数の戻り即時性)を測定し、このアプローチを使用すると、その関数からの非常に高速な応答が得られることがわかりました。

最初の問題は明らかです。JavaScriptをスレッド化できないため、難読化を追加している間は何も獲得できません。次に、テンプレートがレンダリングされたはずであるにもかかわらず、テンプレートがレンダリングされていることを期待する可能性のあるイベントリスナーのスタックから、テンプレートのレンダリングを効果的に切り離しました。その関数の実際の動作は、無意識のうちにそうであるように、関数を実行する、または関数に依存する関数と同様に、非決定性になりました。知識に基づいた推測を行うことはできますが、その動作を適切にコーディングすることはできません。

ロジックに依存する新しいイベントハンドラを作成するときの「修正」は、使用することsetTimeout 0でした。しかし、それは修正ではなく、理解するのが難しく、このようなコードによって引き起こされるエラーをデバッグするのは楽しいことではありません。プラットフォームの現在のパフォーマンスなど、その時点で何が起こっているかに応じて、問題が発生しないこともあれば、一貫して失敗することもあります。これが私が個人的にこのハックを使用しないことを勧める理由です(これハックであり、私たちは皆それを知っている必要があります)。

しかし、代わりに何ができるでしょうか?まあ、参照されたMDN記事が示唆しているように、キューに入れられた他のメッセージが作業中にインターリーブされて実行中に実行されるように、作業を複数のメッセージに分割する(可能な場合)、または実行可能なWebワーカーを使用するあなたのページと連携して、その計算を完了すると結果を返します。

ああ、あなたが「まあ、私はそれを非同期にするために長時間実行する関数にコールバックを入れることができなかったのではないか?」と考えているなら、そうではありません。コールバックは非同期にしません。コールバックを明示的に呼び出す前に、長時間実行されるコードを実行する必要があります。


3

これが行うもう1つのことは、関数呼び出しをスタックの一番下にプッシュし、関数を再帰的に呼び出す場合にスタックオーバーフローを防ぐことです。これにはwhileループの効果がありますが、JavaScriptエンジンは他の非同期タイマーを起動できます。


このへの反対票push the function invocation to the bottom of the stack。あなたstackは何を話しているのか不明瞭です。最も重要なのは、setTimeoutこの関数、このループサイクルの終わり、または次のループサイクルの始まりを正確に追加する場所です。
グリーン

1

setTimeoutを呼び出すことにより、ユーザーが行っていることに反応する時間をページに与えます。これは、ページの読み込み中に実行される関数に特に役立ちます。


1

setTimeoutが役立つその他のいくつかのケース:

実行時間の長いループや計算を小さなコンポーネントに分割して、ブラウザが「フリーズ」したり、「ページのスクリプトがビジーです」と表示されないようにしたりします。

クリックされたときにフォーム送信ボタンを無効にする必要がありますが、onClickハンドラーでボタンを無効にすると、フォームは送信されません。ゼロの時間を持つsetTimeoutはトリックを実行し、イベントを終了させ、フォームの送信を開始させ、ボタンを無効にすることができます。


2
無効化はonsubmitイベントで行うほうが適切です。送信を停止できるため、フォームが技術的に送信される前に、呼び出しが確実に呼び出されます。
Kris

とても本当です。ボタンにonclick = "this.disabled = true"と入力するだけでプロトタイプを作成できるため、onclickを無効にした方が簡単だと思います。
fabspro 14

1

他のいくつかのコードが完了する前に、実行ループとDOMのレンダリングに関する答えは正しいです。JavaScriptのゼロ秒のタイムアウトは、そうでない場合でも、コードを疑似マルチスレッド化するのに役立ちます。

多くのモバイルブラウザーはクロックの制限により20ミリ秒未満のタイムアウトを登録できないため、JavaScriptのクロスブラウザー/クロスプラットフォームゼロ秒タイムアウトのBEST値は実際には0(ゼロ)ではなく約20ミリ秒であることを追加したいと思います。 AMDチップ。

また、DOM操作を含まない実行時間の長いプロセスは、JavaScriptの真のマルチスレッド実行を提供するため、Webワーカーに送信する必要があります。


2
私はあなたの答えに少し懐疑的ですが、ブラウザの標準に関する追加の調査をせざるを得なかったので、それを支持しました。標準を研究するとき、私はいつも行くところに行きます、MDN:developer.mozilla.org/en-US/docs/Web/API/window.setTimeout HTML5仕様は4msと言っています。モバイルチップのクロック制限については何も述べていません。声明をバックアップするための情報源を探すのに苦労しています。Dart Language by GoogleがsetTimeoutを完全に削除し、Timerオブジェクトを優先することを確認しました。
John Zabroski、2013年

8
(...)多くの携帯電話のブラウザが原因クロック制限(...)に20ミリ秒未満のタイムアウトを登録することはできませんので、すべてのプラットフォームは、そのクロックなしプラットフォームに制限をタイミングましたが、次回実行することが可能であることを正確にした後は0ms現在のもの。タイムアウトが0ミリ秒の場合は、できるだけ早く関数の実行を要求します。特定のプラットフォームのタイミング制限によって、この意味が変わることはありません。
Piotr Dobrogost 2013年

1

setTimout on 0は、すぐに返したい遅延プロミスを設定するパターンでも非常に役立ちます。

myObject.prototype.myMethodDeferred = function() {
    var deferredObject = $.Deferred();
    var that = this;  // Because setTimeout won't work right with this
    setTimeout(function() { 
        return myMethodActualWork.call(that, deferredObject);
    }, 0);
    return deferredObject.promise();
}

1

問題は、存在しない要素に対してJavascript操作を実行しようとしたことでした。要素はまだ読み込まれていなかったためsetTimeout()、次の方法で要素が読み込まれるまでの時間が長くなります。

  1. setTimeout()イベントが非同期になるため、すべての同期コードの後に​​実行され、要素をロードするための時間を増やします。のコールバックのような非同期コールバックsetTimeout()は、イベントキューに配置され、イベントループによってスタックに配置されます。同期コードのスタックが空になった後、ます。
  2. 関数の2番目の引数としてのmsの値0 setTimeout()は、多くの場合わずかに高くなります(ブラウザーに応じて4〜10 ms)。setTimeout()コールバックを実行するために必要なこのわずかに長い時間は、イベントループの「ティック」(スタックが空の場合、ティックがスタックにコールバックをプッシュしている)の量が原因です。パフォーマンスとバッテリー寿命の理由により、イベントループのティックの量は、1秒あたり1000回未満の特定の量に制限されています。

-1

Javascriptはシングルスレッドアプリケーションであるため、関数を同時に実行することはできません。このため、このイベントループを使用することができます。つまり、setTimeout(fn、0)が正確に行うことは、コールスタックが空のときに実行されるタスククエストに追い込まれることです。この説明はかなり退屈であることを知っているので、このビデオをご覧になることをお勧めします。これにより、ブラウザの内部で物事がどのように機能するかがわかります。このビデオをチェックしてください:-https : //www.youtube.com/watch?time_continue = 392&v = 8aGhZQkoFbQ

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.