Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create your own setTimeout function?

I understand how to use setTimeout function, but I can't find a way to create a function like it.
I have an example:

setTimeout(() => {
  console.log('3s');
}, 3000);
while(1);

The result is setTimeout callback never call so I think it use the same thread like every js other functions. But when it's check the time reach or not? and how it can do that?

Updated

To avoid misunderstanding I update my question.
I can't find a way to create a async function with callback after specify time (without using setTimeout and don't block entire thread). This function setTimeout seen like a miracle to me. I want to understand how it work.

like image 478
Đinh Anh Huy Avatar asked May 24 '18 05:05

Đinh Anh Huy


3 Answers

Just for the game since I really don't see why you couldn't use setTimeout...


To create a non-blocking timer, without using the setTimeout/setInterval methods, you have only two ways:

  • event based timer
  • run your infinite loop in a second thread

Event based timer

One naive implementation would be to use the MessageEvent interface and polling until the time has been reached. But that's not really advice-able for long timeouts as this would force the event-loop to constantly poll new tasks, which is bad for trees.

function myTimer(cb, ms) {
  const begin = performance.now();
  const channel = myTimer.channel ??= new MessageChannel();
  const controller = new AbortController();
  channel.port1.addEventListener("message", (evt) => {
    if(performance.now() - begin >= ms) {
      controller.abort();
      cb();
    }
    else if(evt.data === begin) channel.port2.postMessage(begin);
  }, { signal: controller.signal });
  channel.port1.start();
  channel.port2.postMessage(begin);
}

myTimer(() => console.log("world"), 2000);
myTimer(() => console.log("hello"), 100);

So instead, if available, one might want to use the Web Audio API and the AudioScheduledSourceNode, which makes great use of the high precision Audio Context's own clock:

function myTimer(cb, ms) {
  if(!myTimer.ctx) myTimer.ctx = new (window.AudioContext || window.webkitAudioContext)();
  var ctx = myTimer.ctx;
  var silence = ctx.createGain();
  silence.gain.value = 0;
  var note = ctx.createOscillator();
  note.connect(silence);
  silence.connect(ctx.destination);
  note.onended = function() { cb() };
  note.start(0);
  note.stop(ctx.currentTime + (ms / 1000));
}

myTimer(()=>console.log('world'), 2000);
myTimer(()=>console.log('hello'), 200);

Infinite loop on a different thread

Yes, using Web Workers we can run infinite loops without killing our web page:

function myTimer(cb, ms) {
  var workerBlob = new Blob([mytimerworkerscript.textContent], {type: 'application/javascript'});
  var url = URL.createObjectURL(workerBlob);
  var worker = new Worker(url);
  worker.onmessage = function() {
    URL.revokeObjectURL(url);
    worker.terminate();
    cb();
  };
  worker.postMessage(ms);
}

myTimer(()=>console.log('world'), 2000);
myTimer(()=>console.log('hello'), 200);
<script id="mytimerworkerscript" type="application/worker-script">
  self.onmessage = function(evt) {
    var ms = evt.data;
    var now = performance.now();
    while(performance.now() - now < ms) {}
    self.postMessage('done');
  }
</script>

And for the ones who like to show off they know about the latest features not yet really available (totally not my style), a little mention of the incoming Prioritized Post Task API and its delayed tasks, which are basically a more powerful setTimeout, returning a promise, on which we can set prioritization.

(async () => {
  if(globalThis.scheduler) {
    const p1 = scheduler.postTask(()=>{ console.log("world"); }, { delay: 2000} );
    const p2 = scheduler.postTask(()=>{ console.log("hello"); }, { delay: 1000} );
    await p2;
    console.log("future");
  }
  else {
    console.log("Your browser doesn't support this API yet");
  }
})();
like image 96
Kaiido Avatar answered Sep 29 '22 08:09

Kaiido


The reason callback of setTimeout() is not being called is, you have while(1) in your code which acts as infinite loop. It will keep your javascript stack busy whole time and that is the reason event loop will never push callback function of setTimeout() in stack.

If you remove while(1) from your code, callback for setTimeout() should get invoked.

setTimeout(() => {
  console.log('3s');
}, 3000);
like image 31
Raeesaa Avatar answered Sep 29 '22 09:09

Raeesaa


To create your own setTimeout function, you can use the following function, setMyTimeout() to do that without using setTimeout.

var foo= ()=>{
  console.log(3,"Called after 3 seconds",new Date().getTime());
}
var setMyTimeOut = (foo,timeOut)=>{
	let timer;
  let currentTime = new Date().getTime();
  let blah=()=>{

      if (new Date().getTime() >= currentTime + timeOut) {
        clearInterval(timer);
        foo()
      }
  }
  timer= setInterval(blah, 100);
}
console.log(1,new Date().getTime());
setMyTimeOut(foo,3000)
console.log(2,new Date().getTime());
like image 21
Rohith Murali Avatar answered Sep 29 '22 07:09

Rohith Murali