繁体   English   中英

setTimeout 过早调用 function

[英]setTimeout calling the function prematurely

我的库有基于实时的测试用例,我注意到测试会随机失败并出现 1 毫秒的错误:

expect(received).toBeGreaterThanOrEqual(expected)

    Expected: >= 1000
    Received:    999

这似乎是由于 setTimeout 过早调用 function 所致。

所以我写了一个单独的测试脚本:

let last = Date.now()

setTimeout(next, 1000)

function next() {
  if (Date.now() - last < 1000) process.exit(1)
  last = Date.now()
  setTimeout(next, 1000)
}

在Node.js v12.19.0、v14.15.3、v15.4.0上,会随机失败:有时脚本可以继续运行,有时脚本会很快退出。 这不仅发生在我的本地计算机上,也发生在 Github 的 CI 服务器上。

我的问题:这是一个错误吗? 还是 setTimeout 的某种预期行为? 还是Date.now() - time总是需要增加 1 毫秒?

更新:另见https://github.com/nodejs/node/issues/26578

更新:这里使用git-bisect是罪魁祸首:

2c409a285359faae58227da283a4c7e5cd9a2f0c is the first bad commit
commit 2c409a285359faae58227da283a4c7e5cd9a2f0c
Date:   Tue Aug 25 13:36:37 2020 -0600

    perf_hooks: add idleTime and event loop util
    
    Use uv_metrics_idle_time() to return a high resolution millisecond timer
    of the amount of time the event loop has been idle since it was
    initialized.
    
    Include performance.eventLoopUtilization() API to handle the math of
    calculating the idle and active times. This has been added to prevent
    accidental miscalculations of the event loop utilization. Such as not
    taking into consideration offsetting nodeTiming.loopStart or timing
    differences when being called from a Worker thread.
    
    PR-URL: https://github.com/nodejs/node/pull/34938

这似乎是一个错误,而不是预期的行为。 我会投票反对总是添加 1ms,因为行为不一致。 (但是,它会早于 1 毫秒吗?我没有观察到超过 1 毫秒)您可以解决以下问题:

const origSetTimeout = setTimeout;
setTimeout = (f, ms, ...args) => {
  let o;
  const when = Date.now() + ms,
    check = ()=> {
      let t = when - Date.now();
      if (t > 0) Object.assign(o, origSetTimeout(check, t));
      else f(...args);
    };
  return o = origSetTimeout(check, ms);
};

即使在解决问题时,它也将允许clearTimeout()

这是一个模拟问题并每 3 秒交替使用解决方法的浏览器代码:

 // Simulate the problem const realOrigSetTimeout = setTimeout; setTimeout = (func, ms, ...args) => realOrigSetTimeout(func, ms - Math.random(), ...args); const ms = 200; let when = Date.now() + ms; setTimeout(next, ms); function next() { let now = Date.now(); setTimeout(next, ms); console.log(now < when? 'premature': 'ok'); when = now + ms; } function workAround() { console.log('Applying workaround'); const origSetTimeout = setTimeout; setTimeout = (f, ms, ...args) => { let o; const when = Date.now() + ms, check = ()=> { let t = when - Date.now(); if (t > 0) Object.assign(o, origSetTimeout(check, t)); else f(...args); }; return o = origSetTimeout(check, ms); }; setTimeout(_=>{ console.log('Removing workaround'); setTimeout = origSetTimeout; setTimeout(workAround, 3000); }, 3000); } setTimeout(workAround, 3000);

下面是一个 nodejs 代码,它将清楚地显示问题(点之间的“p”),并将在按 enter 后应用解决方法。

'use strict';

const ms = 1;
let when = Date.now() + ms;
setTimeout(next, ms);

function next() {
  let now = Date.now();
  setTimeout(next, ms);
  process.stdout.write(now < when ? 'p' : '.');
  when = now + ms;
}

process.stdin.on('readable', _=> {
  console.log('enabling workaround');
  const origSetTimeout = setTimeout;
  setTimeout = (f, ms, ...args) => {
    let o;
    const when = Date.now() + ms,
      check = ()=> {
        let t = when - Date.now();
        if (t > 0) Object.assign(o, origSetTimeout(check, t));
        else f(...args);
      };
    return o = origSetTimeout(check, ms);
  };
});

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM