【问题标题】:Javascript, Promises and setTimeoutJavascript、Promises 和 setTimeout
【发布时间】:2020-08-19 20:49:35
【问题描述】:

我一直在玩 Promises,但我无法理解以下代码发生了什么:

const promise = new Promise((resolve, reject) => {
  console.log('Promise started - Async code started')
  setTimeout(() => {
    resolve('Success')
  }, 10)
})

setTimeout(() => {
  console.log('Promise log inside first setTimeout')
}, 0)

promise.then(res => {
  console.log('Promise log after fulfilled')
})

console.log('Promise made - Sync code terminated')

setTimeout(() => {
  console.log('Promise log inside second setTimeout')
}, 0)

输出是:


Promise started - Async code started 
Promise made - Sync code terminated 
Promise log inside first setTimeout 
Promise log inside second setTimeout 
Promise log after fulfilled

正如预期的那样。

但是让我们检查以下代码的输出:

const promise = new Promise((resolve, reject) => {
  console.log('Promise started - Async code started')
  setTimeout(() => {
    resolve('Success')
  }, 1)
})

setTimeout(() => {
  console.log('Promise log inside first setTimeout')
}, 0)

promise.then(res => {
  console.log('Promise log after fulfilled')
})

console.log('Promise made - Sync code terminated')
setTimeout(() => {
  console.log('Promise log inside second setTimeout')
}, 0)

将要解决的承诺 setTimeout 计时器值从 10 毫秒更改为 1 毫秒

输出是:

Promise started - Async code started 
Promise made - Sync code terminated 
Promise log after fulfilled 
Promise log inside first setTimeout 
Promise log inside second setTimeout 

对此有何解释?

【问题讨论】:

  • Timeouts are throttled。在这种情况下,不同的浏览器(JavaScript 引擎)表现不同。看起来 Chrome 无法区分非常小的延迟和 0
  • What is minimum millisecond value of setTimeout? 的可能重复项。 1 被视为0,在这种情况下,日志再次开始有意义。
  • @Bergi 实际上是 0,在 Chrome 中被视为 1

标签: javascript google-chrome promise settimeout es6-promise


【解决方案1】:

Chrome has an hardcoded minimum timeout of 1ms.

  base::TimeDelta interval_milliseconds =
      std::max(base::TimeDelta::FromMilliseconds(1), interval);

因此,对于 Chrome,您所有的 setTimeout( fn , 0 ) 都会转换为 setTimeout( fn , 1 ),因此会在您安排的第一个之后触发(记住 Promise 构造函数是同步调用的)。

所以我们实际上可以简化您的示例

setTimeout( () => console.log( '1ms delay' ), 1 );
setTimeout( () => console.log( '0ms delay' ), 0 );

在 Chrome 中1ms 延迟总是会首先触发,这与常识相反,因为在内部它实际上是:

setTimeout( () => console.log( '1ms delay' ), Math.max(1, 1) );
setTimeout( () => console.log( '0ms delay' ), Math.max(1, 0) );

如果您将其设置为 12 而不是 01,您的期望将会实现。

const promise = new Promise((resolve, reject) => {
  console.log('Promise started - Async code started')
  setTimeout(() => {
    resolve('Success')
  }, 2)
})

setTimeout(() => {
  console.log('Promise log inside first setTimeout')
}, 1)

promise.then(res => {
  console.log('Promise log after fulfilled')
})

console.log('Promise made - Sync code terminated')
setTimeout(() => {
  console.log('Promise log inside second setTimeout')
}, 1)

【讨论】:

    【解决方案2】:

    我会用下面的例子来解释:

    setTimeout(() => {
      console.log('1 ms timeout');
    }, 1);                            // Moved to async queue at time = T0
    setTimeout(() => {
      console.log('0 ms timeout')
    }, 0);                            // Moved to async queue after 1 ms that synchronous call to setTimeout takes i.e. at T1
                                      // So at T1, queue will be [("1ms timeout", 0), ("0ms timeout", 0)]
    

    因此会打印出来

    1 ms timeout
    0 ms timeout
    

    上面的理解: 调用 setTimeouts 是同步的(即使它的回调放在异步队列中),即我们调用 setTimeout() 并移动到下一条语句 - 这个同步操作本身可能需要 1 毫秒。

    换句话说,1ms 太短了,所以当 JS 引擎看到第二个异步语句时,第一个已经在队列中花费了 1ms。

    我还建议您尝试以下方法

    setTimeout(() => {
      console.log("First");
    }, 2);                      // queue at T0 = [("First", 2)]
    
    const forLoopLimit = 100;
    for (var i = 0; i < forLoopLimit; i++){
        console.log(i * 10000);
    }                           // Assume that it takes about 3 milliseconds
                                // queue at T3 = [("First", 0)]
    setTimeout(() => {
      console.log("Second");
    }, 0);                      // Assume it takes 0 milliseconds.
                                // queue at T4 = [("First", 0), ("Second", 0)]
    

    这将在Second 之前打印First,即使前者的超时时间为2ms,而后者的超时时间为0ms。 现在把forLoopLimit改成1甚至10,你会看到同步任务现在不需要3毫秒,而且Second打印在First之前

    另外值得一试的是:

    console.log(Date.now());
    console.log(Date.now());
    

    多次尝试上述方法,您会发现有时控制台日志会有不同的时间戳。粗略地说,你可以说console.log()Date.now() 需要 0.5 毫秒。只是调用/执行同步内容的时间。

    【讨论】:

    • 1ms 调用 setTimeout?严重地? Nhaa,你可能在一个非常幸运的日子里,你在 T00:00:00.00999 打电话给第一个......而第二个在 T:00:00:00.0100 但就是这样,不会是确定性行为但随机。
    【解决方案3】:

    来自Concurrency model and the event loop

    • setTimeout 在计时器到期后不会立即运行
    • 零延迟实际上并不意味着回调将在零毫秒后触发。以 0(零)的延迟调用 setTimeout 毫秒后不执行回调函数 间隔。 基本上,setTimeout 需要等待队列消息的所有代码完成,即使您指定了特定的时间限制 为您的 setTimeout。

    如果我们设置 2 和 1 毫秒会发生什么:

    const promise = new Promise((resolve, reject) => {
      console.log('Promise started - Async code started')
      setTimeout(() => {
        resolve('Success')
      }, 2)
    })
    
    console.log('Promise log inside first setTimeout 1')
    setTimeout(() => {
      console.log('Promise log inside first setTimeout 2')
    }, 1)
    
    promise.then(res => {
      console.log('Promise log after fulfilled ❌')
    
    })
    
    console.log('Promise log inside second setTimeout 1')
    setTimeout(() => {
      console.log('Promise log inside second setTimeout 2')
    }, 1)
    });
    

    输出总是:

    Promise started - Async code started
    Promise log inside first setTimeout 1
    Promise log inside second setTimeout 1
    Promise log inside first setTimeout 2
    Promise log inside second setTimeout 2
    Promise log after fulfilled ❌
    

    结论

    如果您想要正确的行为,值得摆脱零延迟。

    【讨论】:

    • 这仍然不能解释为什么 1ms 超时被安排在 0ms 之前触发,即使在回调执行之前发生的事情需要很长时间,它们应该被安排在 0ms 和 1ms 之后,只是就像您的 1 和 2 示例一样,它们很可能不会在安排好后的 1 毫秒和 2 毫秒时触发。 jsfiddle.net/63km472d
    猜你喜欢
    • 1970-01-01
    • 2019-02-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-25
    • 1970-01-01
    相关资源
    最近更新 更多