0

I have a setTimeout that adds an eventListener, but if an action occur i want to remove the eventListener.

let to;
function() fun{
    to = setTimeout(() => el.addEventListener('mouseover', foo), 500);
}
fun();
windows.addEventListener('mouseup', function(removeListener){
    clearTimeout(to);
    el.removeEventListener('mouseover', foo)
})

I remove the event listener because the code from the timeout could have already started and still to proceed attaching the event before the clear, but is there a case where they collide and the addEventListener line is still executed after the remove line. So my question is does the setTimeout and the addEventListener code happen on the same thread and is there a way they execute at the same time?

Another example is if I have 2 settimeouts both happening after 500ms, how do I know that the removing of the listener will happen after the adding.

setTimeout(() => el.addEventListener('mouseover', foo), 500);
setTimeout(() => el.removeEventListener('mouseover', foo), 500);
  • @HereticMonkey sorry it was just a fast example of what I meant, but this is not really my question. I edited the example. –  Jan 21 '20 at 22:54
  • There's only one thread (apart from Workers) in browser-based JavaScript. `setTimeout` works by scheduling things on that thread. It is definitely possible for the user to mouse up on the window within 500 milliseconds. – Heretic Monkey Jan 21 '20 at 22:59
  • @HereticMonkey yes, but can the mouseup not clear the timeout because the code has already began executing and somehow the addlistener to be added after the remove line (to happen at the same time) –  Jan 21 '20 at 23:00
  • @HereticMonkey or if they both happen at the same time after 500ms will the removeListener wait for the code from the already finished settimout to finish executing (same thread). –  Jan 21 '20 at 23:05
  • @HereticMonkey I added another example if it can describe it better for you. What if this is the same scenario with the mouseup they both happen at the same time(after 500ms) –  Jan 21 '20 at 23:09
  • The fact of the matter is that you can't know which will occur first. It's practically the definition of a "race condition". Ultimately, the JavaScript engine will choose one to "win" and run it first. More at [Execution order of multiple setTimeout() functions with same interval](https://stackoverflow.com/q/11771558/215552) – Heretic Monkey Jan 21 '20 at 23:20

2 Answers2

0

JavaScript is single-threaded. Therefore, unless you use async or use the Worker API, you usually don't have to worry about race conditions and your first code would work just fine (but see this answer).

In other words, two code executions cannot happen at the same time in either of your examples. Instead, one of them happens either before or after another but that order is not guaranteed. Therefore either clearTimeout or removeEventListener alone is not sufficient but doing both would do the job. Meanwhile, the timeout callback won't be called in between the two function calls and the JavaScript engine would wait for any pending execution to finish even if the timeout has already expired. Therefore if you have an infinite loop somewhere your timeouts will never be called (besides the more serious effect that your entire page just freezes). The pretty much only possible way of interleaved execution is when one function calls another.

Plus, clearTimeout cancels the call even if it has already been expired, so cancelling setTimeout(fn, 0) is possible so that fn would not be called.

For the second part, there's no guarantee that setTimeout would execute in any order.

Jin-oh Kang
  • 317
  • 1
  • 8
  • But how are two timeouts at the same time different from eventlistener that happens at the same moment as the settimeout? They can also occur at the same time. –  Jan 22 '20 at 00:18
  • @heyza22 One way to think about this is that even if you do `setTimeout(fn, 500)` the function won't be called **exactly** after 0.5 second. Furthurmore, if there's *any* JavaScript code running then the timeout would wait for it to finish in order to run `fn`. – Jin-oh Kang Jan 22 '20 at 00:32
  • But if they both happen at 500ms won't there still be race condition and if remove event listener wins the race condition then the next on the que for execution will be the timeout and clearTimeout will be useless because the 500ms already have passed and the function of the timeout is on the stack or however it is called in js? –  Jan 22 '20 at 00:35
  • @heyza22 I repeat, there's **only** a single thread. Both `addEventListener` and `setTimeout` would compete to put jobs in *that* sole thread, waiting if one is already executing. A browser may have multiple threads, but JavaScript code is only permitted to run in **one** dedicated thread, and any new events have to be queued if the thread is already occupied. – Jin-oh Kang Jan 22 '20 at 00:37
  • @heyza22 Ah, I get it. Even when the `500ms` timeout expired and the timer callback is ready to run, `clearTimeout` would cancel it. – Jin-oh Kang Jan 22 '20 at 00:39
  • yes but race condition doesn't amplify multiple stacks what I mean is that the eventListener and timeout both happen at 500ms then there is a race condition. So if eventListener wins the race condition both the listener and timeout are still pushed in the function stack call and the clearTimeout won't matter. –  Jan 22 '20 at 00:40
  • @heyza22 After posting the first comment I realized what you meant. Please see my second comment. In short, `let to=setTimeout(alert, 0); for(let i=0,s=0;i<50000;i++){s+=i;} clearTimeout(to);` won't cause `alert` to be called, even that `0ms` would immediately expire. – Jin-oh Kang Jan 22 '20 at 00:44
0

For the first part, since you do both clear the timeout and remove the possibly added event listener, there is no way that after the mouseup event handler has been called the mouseover one is still there, nor will it be added later.

On each js context a single thread is used and there is no way two operations are made at the same time.

So there are only two cases:

  • mouseup fires after the timeout => handled by removeEventListener.
  • mouseup fires before the timeout => clearTimeout will prevent the event from ever being attached.

For the second part, since both timeouts share the same delay, you can be sure the first one will get executed first. That wouldn't be the case with for instance 500 & 499, because in normal case you'd have the 499 one fire before, but if it took more than one ms between the two calls, then the 500 one will fire before.

__

But now, if I may, instead of relying on timeouts to add and remove event listeners, it might be easier to simply add your event listener, and in the handler, check a semaphore variable that you can control as you wish.

let semaphore = false;
el.addEventListener('mouseover', foo);

let timeout = setTimeout(()=> { semaphore = true; }, 500);

window.addEventListener('mouseup', ()=> {
  clearTimeout(timeout);
  semaphore = false;
});

function foo(evt) {
  if(!semaphore) { return; }
  // ... do something if we are allowed to
}
Kaiido
  • 123,334
  • 13
  • 219
  • 285