高级前端js【Q688】setTimeout为什么最小只能设置4ms,如何实现一个0ms的setTimeout?

setTimeout为什么最小只能设置4ms,如何实现一个0ms的setTimeout?

更多描述 可参考两篇文章

  1. 为什么 setTimeout 有最小时延 4ms ?
  2. 如何实现一个0ms的setTimeout?

Issue 欢迎在 Gtihub Issue 中回答此问题: Issue 708

Author 回答者: shfshanyue

postMessage

Author 回答者: hengistchan

blink引擎的DOMTimer类源码 前几天刚好找了一下源码,在47-49行设置了kMaxTimerNestingLevelkMinimumInterval 两个变量分别为5和4, 分别表示最大的嵌套层数和最小的毫秒数 image 截取一下DOMTimer类的部分代码

DOMTimer::DOMTimer(ExecutionContext* context,
                   ScheduledAction* action,
                   base::TimeDelta timeout,
                   bool single_shot,
                   int timeout_id)
    : ExecutionContextLifecycleObserver(context),
      TimerBase(nullptr),
      timeout_id_(timeout_id),
      // Step 9:
      nesting_level_(context->Timers()->TimerNestingLevel()),
      action_(action) {
  DCHECK_GT(timeout_id, 0);
 
  // Step 10:
  if (timeout < base::TimeDelta())
    timeout = base::TimeDelta();
 
  // Steps 12 and 13:
  // Note: The implementation increments the nesting level before using it to
  // adjust timeout, contrary to what the spec requires crbug.com/1108877.
  IncrementNestingLevel();
 
  // Step 11:
  // Note: The implementation uses >= instead of >, contrary to what the spec
  // requires crbug.com/1108877.
  if (nesting_level_ >= kMaxTimerNestingLevel && timeout < kMinimumInterval)
    timeout = kMinimumInterval;
 
  // Select TaskType based on nesting level.
  TaskType task_type;
  if (timeout.is_zero()) {
    task_type = TaskType::kJavascriptTimerImmediate;
    DCHECK_LT(nesting_level_, kMaxTimerNestingLevel);
  } else if (nesting_level_ >= kMaxTimerNestingLevel) {
    task_type = TaskType::kJavascriptTimerDelayedHighNesting;
  } else {
    task_type = TaskType::kJavascriptTimerDelayedLowNesting;
  }
  MoveToNewTaskRunner(context->GetTaskRunner(task_type));
 
  // Clamping up to 1ms for historical reasons crbug.com/402694.
  timeout = std::max(timeout, base::TimeDelta::FromMilliseconds(1));

看代码中setp11的那部分,当嵌套层数大于5且timeout小于4ms时,timeout才会等于4ms 然后(代码最后一行),timeout 还会和1ms作比较取最大值,作为最终的timeout

Author 回答者: DanielLeefu

let timeouts = [];
const messageName = 'zero-settimeout'

function setTimeoutZero(fn) {
  timeouts.push(fn);
  window.postMessage(messageName, '*')
}

function handleMessage (evt) {
  if (evt.source == window && evt.data === messageName ) {
    if (timeouts.length > 0) {
      const f = timeouts.shift()
      f()
    }
  }
}

window.addEventListener('message', handleMessage)

window.zeroSettimeout = setTimeoutZero;