How to create your own setTimeout function?

2019-07-29 17:14发布

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.

5条回答
走好不送
2楼-- · 2019-07-29 17:33

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...

function myTimer(cb, ms) {
  var now = performance.now();
  window.addEventListener('message', handleMessage);
  postMessage('myTimer', '*');
  function handleMessage(evt) {
   if(evt.data === 'myTimer') {
      if(performance.now() - now >= ms) {
        window.removeEventListener('message', handleMessage);
        cb();
      }
      else {
        postMessage('myTimer', '*');
      }
    }
  }

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

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>

查看更多
欢心
3楼-- · 2019-07-29 17:39

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

查看更多
闹够了就滚
4楼-- · 2019-07-29 17:39

Following is the implementation of custom setTimeout and setInterval, clearTimeout and clearInterval. I created them to use in sandbox environments where builtin setTimeout and setInterval doesn't work.

const setTimeouts = [];
export function customSetTimeout(cb, interval) {
  const now = window.performance.now();
  const index = setTimeouts.length;
  setTimeouts[index] = () => {
    cb();
  };
  setTimeouts[index].active = true;
  const handleMessage = (evt) => {
    if (evt.data === index) {
      if (window.performance.now() - now >= interval) {
        window.removeEventListener('message', handleMessage);
        if (setTimeouts[index].active) {
          setTimeouts[index]();
        }
      } else {
        window.postMessage(index, '*');
      }
    }
  };
  window.addEventListener('message', handleMessage);
  window.postMessage(index, '*');
  return index;
}

export function customClearTimeout(setTimeoutId) {
  if (setTimeouts[setTimeoutId]) {
    setTimeouts[setTimeoutId].active = false;
  }
}

const setIntervals = [];
export function customSetInterval(cb, interval) {
  const intervalId = setIntervals.length;
  setIntervals[intervalId] = function () {
    if (setIntervals[intervalId].active) {
      cb();
      customSetTimeout(setIntervals[intervalId], interval);
    }
  };
  setIntervals[intervalId].active = true;
  customSetTimeout(setIntervals[intervalId], interval);
  return intervalId;
}

export function customClearInterval(intervalId) {
  if (setIntervals[intervalId]) {
    setIntervals[intervalId].active = false;
  }
}
查看更多
看我几分像从前
5楼-- · 2019-07-29 17:46

Hi you can try this. ] HOpe it will help. Thanks

function customSetTimeOut (callback, ms) {
        var dt = new Date();
        var i = dt.getTime();
        var future = i + ms;
        while(Date.now() <= future) {
            //do nothing - blocking
        }
        return callback();
}

customSetTimeOut(function(){
  console.log("Timeout success");
},1000);

查看更多
仙女界的扛把子
6楼-- · 2019-07-29 17:50

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

查看更多
登录 后发表回答