Development Tip

setTimeout (fn, 0)이 때때로 유용한 이유는 무엇입니까?

yourdevel 2020. 9. 28. 10:22
반응형

setTimeout (fn, 0)이 때때로 유용한 이유는 무엇입니까?


최근에 코드가 <select>JavaScript를 통해 동적으로 로드되는 다소 불쾌한 버그가 발생했습니다 . 이 동적로드 <select>에는 미리 선택된 값이 있습니다. IE6에서는 <option>때때로 <select>selectedIndex값이 아래와 같이 선택된 <option>index속성 과 동기화되지 않기 때문에 selected를 수정하는 코드가 이미있었습니다 .

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()있습니까?


이것은 협동 멀티 태스킹을하기 때문에 작동합니다.

브라우저는 거의 모든 작업을 한 번에 수행해야하며 그중 하나만 JavaScript를 실행합니다. 그러나 JavaScript가 자주 사용되는 것 중 하나는 브라우저에 표시 요소를 빌드하도록 요청하는 것입니다. 이것은 종종 동기식으로 수행되는 것으로 간주되지만 (특히 JavaScript가 병렬로 실행되지 않기 때문에) 이것이 사실임을 보장 할 수 없으며 JavaScript에는 대기를위한 잘 정의 된 메커니즘이 없습니다.

해결책은 렌더링 스레드가 따라 잡을 수 있도록 JavaScript 실행을 "일시 중지"하는 것입니다. 이것이 setTimeout()타임 아웃이 0 인 경우의 효과입니다 . C의 스레드 / 프로세스 수율과 비슷합니다. "즉시 실행"이라고 말하는 것처럼 보이지만 실제로는 브라우저가이 새로운 JavaScript 조각에 참석하기 전에 완료되기를 기다리고 있던 일부 비 JavaScript 작업을 완료 할 수있는 기회를 제공합니다. .

(실제로 setTimeout()는 실행 대기열 끝에 새 JavaScript를 다시 대기열에 넣습니다 . 자세한 설명은 주석을 참조하십시오.)

IE6는이 오류가 발생하기 더 쉽습니다 만, 이전 버전의 Mozilla와 Firefox에서 발생하는 것을 보았습니다.


Philip Roberts의 "이벤트 루프가 도대체 ​​무엇입니까?" 자세한 설명을 위해.


머리말:

중요 참고 : 가장 많이 찬성되고 수락되었지만 @staticsan이 수락 한 답변은 실제로 정확 하지 않습니다! -이유에 대한 설명은 David Mulder의 설명을 참조하십시오.

다른 답변 중 일부는 정확하지만 실제로 해결되는 문제가 무엇인지 설명하지 않으므로 자세한 설명을 제공하기 위해이 답변을 만들었습니다.

따라서, 나는 게시하고 자세한 워크를 통해 브라우저가 수행하는 작업을하고 사용하는 방법을 setTimeout()하는 데 도움이 . 길어 보이지만 실제로는 매우 간단하고 간단합니다. 방금 아주 자세하게 만들었습니다.

업데이트 : 아래 설명을 라이브 시연하기 위해 JSFiddle을 만들었습니다 : http://jsfiddle.net/C2YBE/31/ . 많은 감사 를 킥 스타트 돕는 @ThangChung합니다.

UPDATE2 : JSFiddle 웹 사이트가 죽거나 코드를 삭제하는 경우를 대비하여 마지막에이 답변에 코드를 추가했습니다.


세부 사항 :

"무언가"버튼과 결과 div가있는 웹 앱을 상상해보십시오.

onClick"무엇인가"버튼 핸들러는 "LongCalc ()"함수를 호출하여 다음 두 가지 작업을 수행합니다.

  1. 매우 긴 계산 (예 : 3 분 소요)

  2. 계산 결과를 결과 div에 인쇄합니다.

이제 사용자가 테스트를 시작하고 "무엇인가"버튼을 클릭하면 페이지가 3 분 동안 아무 일도하지 않는 것처럼 보입니다. 그들은 불안해하고, 버튼을 다시 클릭하고, 1 분을 기다립니다. 아무 일도 일어나지 않습니다. 버튼을 다시 클릭합니다.

문제는 분명합니다. 무슨 일이 일어나고 있는지 보여주는 "상태"DIV가 필요합니다. 어떻게 작동하는지 봅시다.


따라서 "Status"DIV (처음에는 비어 있음)를 추가하고 onclick핸들러 (function LongCalc())를 수정하여 4 가지 작업을 수행합니다.

  1. 상태 DIV에 "계산 중 ... 3 분 정도 걸릴 수 있음"상태를 채 웁니다.

  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 변경에 의해 트리거되는 새 이벤트가 필요합니다.이 이벤트는 대기열의 끝에서 진행됩니다 .
  • 문제!!! 문제!!! 자세한 내용은 아래에 설명되어 있습니다.
  • 이벤트 : 처리기에서 두 번째 줄을 실행합니다 (계산). 이후 대기열 : [Execute OnClick handler(lines 3-4), re-draw Status DIV with "Calculating" value].
  • 이벤트 : 핸들러에서 세 번째 줄을 실행합니다 (결과 DIV 채우기). 이후 대기열 : [Execute OnClick handler(line 4), re-draw Status DIV with "Calculating" value, re-draw result DIV with result].
  • 이벤트 : 핸들러에서 네 번째 줄을 실행합니다 (상태 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를 다시 그립니다. 다시 그리기를 수행하고 대기열에서 제거합니다.
  • 이벤트 : 결과 값으로 Result DIV를 다시 그립니다. 다시 그리기를 수행하고 대기열에서 제거합니다.
  • 이벤트 : "완료"값으로 상태 DIV를 다시 그립니다. 다시 그리기를 수행하고 대기열에서 제거합니다. 샤프 눈 시청자 심지어 통지 값은 마이크로의 일부분에 대한 깜박이는 "계산"을 가진 상태 DIV는 "수 - 계산이 완료된 후에

따라서 근본적인 문제는 "상태"DIV에 대한 다시 그리기 이벤트가 마지막에 큐에 배치되고, 3 분이 걸리는 "실행 줄 2"이벤트 이후에 배치되므로 실제 다시 그리기는 계산이 완료된 후.


구출을 위해 setTimeout(). 어떻게 도움이 되나요? 를 통해 장기 실행 코드를 호출 setTimeout하면 실제로 setTimeout실행 자체와 실행중인 코드에 대한 별도의 큐 항목 (0 시간 초과로 인해)의 두 가지 이벤트가 생성 됩니다.

따라서 문제를 해결하려면 onClick처리기를 두 개의 문으로 수정합니다 (새 함수 또는 내부 블록 onClick).

  1. 상태 DIV에 "계산 중 ... 3 분 정도 걸릴 수 있음"상태를 채 웁니다.

  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].
  • 이벤트 : 핸들러에서 두 번째 줄을 실행합니다 (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);
});

How JavaScript Timers Work 에 대한 John Resig의 기사를 살펴보십시오 . 시간 제한을 설정하면 엔진이 현재 호출 스택을 실행할 때까지 실제로 비동기 코드를 큐에 넣습니다.


setTimeout() 가 0으로 설정되어 있어도 DOM 요소가로드 될 때까지 시간을 벌 수 있습니다.

Check this out: setTimeout


Most browsers have a process called main thread, that is responsible for execute some JavaScript tasks, UI updates e.g.: painting, redraw or reflow, etc.

Some JavaScript execution and UI update tasks are queued to the browser message queue, then are dispatched to the browser main thread to be executed.

When UI updates are generated while the main thread is busy, the tasks are added into the message queue.

setTimeout(fn, 0); add this fn to the end of the queue to be executed. It schedules a task to be added on the message queue after a given amount of time.


There are conflicting upvoted answers here, and without proof there is no way to know whom to believe. Here is proof that @DVK is right and @SalvadorDali is incorrect. The latter claims:

"And here is why: it is not possible to have setTimeout with a time delay of 0 milliseconds. The Minimum value is determined by the browser and it is not 0 milliseconds. Historically browsers sets this minimum to 10 milliseconds, but the HTML5 specs and modern browsers have it set at 4 milliseconds."

The 4ms minimum timeout is irrelevant to what is happening. What really happens is that setTimeout pushes the callback function to the end of the execution queue. If after setTimeout(callback, 0) you have blocking code which takes several seconds to run, the callback will not be executed for several seconds, until the blocking code has finished. Try this code:

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

Output is:

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

One reason to do that is to defer the execution of code to a separate, subsequent event loop. When responding to a browser event of some kind (mouse click, for example), sometimes it's necessary to perform operations only after the current event is processed. The setTimeout() facility is the simplest way to do it.

edit now that it's 2015 I should note that there's also requestAnimationFrame(), which isn't exactly the same but it's sufficiently close to setTimeout(fn, 0) that it's worth mentioning.


This is an old questions with old answers. I wanted to add a new look at this problem and to answer why is this happens and not why is this useful.

So you have two functions:

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

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

and then call them in the following order f1(); f2(); just to see that the second one executed first.

And here is why: it is not possible to have setTimeout with a time delay of 0 milliseconds. The Minimum value is determined by the browser and it is not 0 milliseconds. Historically browsers sets this minimum to 10 milliseconds, but the HTML5 specs and modern browsers have it set at 4 milliseconds.

If nesting level is greater than 5, and timeout is less than 4, then increase timeout to 4.

Also from mozilla:

To implement a 0 ms timeout in a modern browser, you can use window.postMessage() as described here.

P.S. information is taken after reading the following article.


Since it is being passed a duration of 0, I suppose it is in order to remove the code passed to the setTimeout from the flow of execution. So if it's a function that could take a while, it won't prevent the subsequent code from executing.


Both of these two top-rated answers are wrong. Check out the MDN description on the concurrency model and the event loop, and it should become clear what's going on (that MDN resource is a real gem). And simply using setTimeout can be adding unexpected problems in your code in addition to "solving" this little problem.

What's actually going on here is not that "the browser might not be quite ready yet because concurrency," or something based on "each line is an event that gets added to the back of the queue".

The jsfiddle provided by DVK indeed illustrates a problem, but his explanation for it isn't correct.

What's happening in his code is that he's first attaching an event handler to the click event on the #do button.

Then, when you actually click the button, a message is created referencing the event handler function, which gets added to the message queue. When the event loop reaches this message, it creates a frame on the stack, with the function call to the click event handler in the jsfiddle.

And this is where it gets interesting. We're so used to thinking of Javascript as being asynchronous that we're prone to overlook this tiny fact: Any frame has to be executed, in full, before the next frame can be executed. No concurrency, people.

What does this mean? It means that whenever a function is invoked from the message queue, it blocks the queue until the stack it generates has been emptied. Or, in more general terms, it blocks until the function has returned. And it blocks everything, including DOM rendering operations, scrolling, and whatnot. If you want confirmation, just try to increase the duration of the long running operation in the fiddle (e.g. run the outer loop 10 more times), and you'll notice that while it runs, you cannot scroll the page. If it runs long enough, your browser will ask you if you want to kill the process, because it's making the page unresponsive. The frame is being executed, and the event loop and message queue are stuck until it finishes.

So why this side-effect of the text not updating? Because while you have changed the value of the element in the DOM — you can console.log() its value immediately after changing it and see that it has been changed (which shows why DVK's explanation isn't correct) — the browser is waiting for the stack to deplete (the on handler function to return) and thus the message to finish, so that it can eventually get around to executing the message that has been added by the runtime as a reaction to our mutation operation, and in order to reflect that mutation in the UI.

This is because we are actually waiting for code to finish running. We haven't said "someone fetch this and then call this function with the results, thanks, and now I'm done so imma return, do whatever now," like we usually do with our event-based asynchronous Javascript. We enter a click event handler function, we update a DOM element, we call another function, the other function works for a long time and then returns, we then update the same DOM element, and then we return from the initial function, effectively emptying the stack. And then the browser can get to the next message in the queue, which might very well be a message generated by us by triggering some internal "on-DOM-mutation" type event.

The browser UI cannot (or chooses not to) update the UI until the currently executing frame has completed (the function has returned). Personally, I think this is rather by design than restriction.

Why does the setTimeout thing work then? It does so, because it effectively removes the call to the long-running function from its own frame, scheduling it to be executed later in the window context, so that it itself can return immediately and allow the message queue to process other messages. And the idea is that the UI "on update" message that has been triggered by us in Javascript when changing the text in the DOM is now ahead of the message queued for the long-running function, so that the UI update happens before we block for a long time.

Note that a) The long-running function still blocks everything when it runs, and b) you're not guaranteed that the UI update is actually ahead of it in the message queue. On my June 2018 Chrome browser, a value of 0 does not "fix" the problem the fiddle demonstrates — 10 does. I'm actually a bit stifled by this, because it seems logical to me that the UI update message should be queued up before it, since its trigger is executed before scheduling the long-running function to be run "later". But perhaps there're some optimisations in the V8 engine that may interfere, or maybe my understanding is just lacking.

Okay, so what's the problem with using setTimeout, and what's a better solution for this particular case?

First off, the problem with using setTimeout on any event handler like this, to try to alleviate another problem, is prone to mess with other code. Here's a real-life example from my work:

A colleague, in a mis-informed understanding on the event loop, tried to "thread" Javascript by having some template rendering code use setTimeout 0 for its rendering. He's no longer here to ask, but I can presume that perhaps he inserted timers to gauge the rendering speed (which would be the return immediacy of functions) and found that using this approach would make for blisteringly fast responses from that function.

First problem is obvious; you cannot thread javascript, so you win nothing here while you add obfuscation. Secondly, you have now effectively detached the rendering of a template from the stack of possible event listeners that might expect that very template to have been rendered, while it may very well not have been. The actual behaviour of that function was now non-deterministic, as was — unknowingly so — any function that would run it, or depend on it. You can make educated guesses, but you cannot properly code for its behaviour.

The "fix" when writing a new event handler that depended on its logic was to also use setTimeout 0. But, that's not a fix, it is hard to understand, and it is no fun to debug errors that are caused by code like this. Sometimes there's no problem ever, other times it concistently fails, and then again, sometimes it works and breaks sporadically, depending on the current performance of the platform and whatever else happens to going on at the time. This is why I personally would advise against using this hack (it is a hack, and we should all know that it is), unless you really know what you're doing and what the consequences are.

But what can we do instead? Well, as the referenced MDN article suggests, either split the work into multiple messages (if you can) so that other messages that are queued up may be interleaved with your work and executed while it runs, or use a web worker, which can run in tandem with your page and return results when done with its calculations.

Oh, and if you're thinking, "Well, couldn't I just put a callback in the long-running function to make it asynchronous?," then no. The callback doesn't make it asynchronous, it'll still have to run the long-running code before explicitly calling your callback.


The other thing this does is push the function invocation to the bottom of the stack, preventing a stack overflow if you are recursively calling a function. This has the effect of a while loop but lets the JavaScript engine fire other asynchronous timers.


By calling setTimeout you give the page time to react to the whatever the user is doing. This is particularly helpful for functions run during page load.


Some other cases where setTimeout is useful:

You want to break a long-running loop or calculation into smaller components so that the browser doesn't appear to 'freeze' or say "Script on page is busy".

You want to disable a form submit button when clicked, but if you disable the button in the onClick handler the form will not be submitted. setTimeout with a time of zero does the trick, allowing the event to end, the form to begin submitting, then your button can be disabled.


The answers about execution loops and rendering the DOM before some other code completes are correct. Zero second timeouts in JavaScript help make the code pseudo-multithreaded, even though it is not.

I want to add that the BEST value for a cross browser / cross platform zero-second timeout in JavaScript is actually about 20 milliseconds instead of 0 (zero), because many mobile browsers can't register timeouts smaller than 20 milliseconds due to clock limitations on AMD chips.

Also, long-running processes that do not involve DOM manipulation should be sent to Web Workers now, as they provide true multithreaded execution of JavaScript.


setTimout on 0 is also very useful in the pattern of setting up a deferred promise, which you want to return right away:

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();
}

The problem was you were trying to perform a Javascript operation on a non existing element. The element was yet to be loaded and setTimeout() gives more time for an element to load in the following ways:

  1. setTimeout() causes the event to be ansynchronous therefore being executed after all the synchronous code, giving your element more time to load. Asynchronous callbacks like the callback in setTimeout() are placed in the event queue and put on the stack by the event loop after the stack of synchronous code is empty.
  2. The value 0 for ms as a second argument in function setTimeout() is often slightly higher (4-10ms depending on browser). This slightly higher time needed for executing the setTimeout() callbacks is caused by the amount of 'ticks' (where a tick is pushing a callback on the stack if stack is empty) of the event loop. Because of performance and battery life reasons the amount of ticks in the event loop are restricted to a certain amount less than 1000 times per second.

Javascript is single threaded application so that don't allow to run function concurrently so to achieve this event loops are use. So exactly what setTimeout(fn, 0) do that its pussed into task quest which is executed when your call stack is empty. I know this explanation is pretty boring, so i recommend you to go through this video this will help you how things work under the hood in browser. Check out this video:- https://www.youtube.com/watch?time_continue=392&v=8aGhZQkoFbQ

참고URL : https://stackoverflow.com/questions/779379/why-is-settimeoutfn-0-sometimes-useful

반응형