承诺与 setTimeout

Posted

技术标签:

【中文标题】承诺与 setTimeout【英文标题】:Promise vs setTimeout 【发布时间】:2016-12-09 16:59:30 【问题描述】:

我在以下代码中观察到了这一点:

setTimeout(function()console.log('setTimeout'));
Promise.resolve(1).then(function()console.log('promise resolve'))

无论我执行多少次,promise 回调总是在 setTimeout 之前记录。

我的理解是,这两个回调都计划在下一个滴答声中执行,我真的不明白发生了什么使得承诺总是优先于超时。

【问题讨论】:

Promise 在创建后立即获得resolved,而setTimeout 在队列中稍后出现.. 阅读内部队列/事件循环:developer.mozilla.org/en-US/docs/Web/javascript/EventLoop、blog.carbonfive.com/2013/10/27/… 或观看此视频:youtube.com/watch?v=8aGhZQkoFbQ 这能回答你的问题吗? Difference between microtask and macrotask within an event loop context 【参考方案1】:

超时和承诺有不同的用途。

setTimeout 将代码块的执行延迟特定的持续时间。 Promise 是一个允许异步执行代码的接口。

promise 允许代码在您等待另一个操作完成时继续执行。通常这是一个网络调用。因此,一旦网络调用(或任何正在等待的承诺)完成,您的 then() 调用中的任何内容都将被执行。 Promise 的开始和 Promise 的解决之间的时间差完全取决于 Promise 正在执行的内容,并且可以随着每次执行而改变。

promise 在超时之前执行的原因是,promise 实际上并没有等待任何东西,所以它立即解决了。

【讨论】:

按照这个逻辑,在这段代码中:Promise.resolve(2).then(function()console.log('promise resolve 2')); console.log('Immediate') "promise resolve 2" 会在 "immediate" 之前记录,对吗?但它没有 @frankies 这与 Promise 的排队和解决方式有关。我回答的重点是setTimeoutPromise的区别。【参考方案2】:

简答 Promises 比事件循环堆栈中的 setTimeout 回调函数具有更高的优先级(或者我如何理解)。

长答案观看此视频。非常有帮助。希望这会有所帮助。

https://www.youtube.com/watch?v=8aGhZQkoFbQ

感谢 @MickJuice 提供用于事件循环的新视频和更新视频。

https://www.youtube.com/watch?v=cCOL7MC4Pl0

【讨论】:

这是一个很棒的视频。这也是帮助您了解任务和事件优先级的方法:youtube.com/watch?v=cCOL7MC4Pl0 在另一个答案的评论中提到的一篇好文章:jakearchibald.com/2015/tasks-microtasks-queues-and-schedules【参考方案3】:

setTimeout() 有一个minimum delay of 4ms,因此即使您没有在代码中指定延迟,超时仍将延迟至少 4 毫秒。同时调用了你的 promise 的 .then()

【讨论】:

即使没有 4ms 的节流优化,promise 还是会更快。因为setTimeout 创建任务,Promise 创建微任务(作业)。这是真正的原因。 嗯...好吧,但实际上 4ms 的最小值确实适用,因此对于诸如宏与微任务先验的问题中的示例不适用。如果我们要想象与实际实现的行为不同的行为,那么即使承诺的优先级低于超时,示例中的承诺仍将首先记录,因为最小超时为 4 毫秒。尽管如此,回到现实世界,知道微任务概念存在于 JS 中还是很有用的,因此感谢您提及它。 meantime ...更准确地说:在下一个滴答声【参考方案4】:

Promise.resolve 调度一个微任务,而 setTimeout 调度一个宏任务。并且在运行下一个宏任务之前执行微任务。

【讨论】:

这是最接近的答案。我会添加对任务/微任务的深入解释的链接。 jakearchibald.com/2015/tasks-microtasks-queues-and-schedules 另外值得一提的是,微任务的来源只能是两件事:MutationObserver 和 Promises。【参考方案5】:

TimeoutsPromises 都用于以异步方式执行代码,但具有不同的特性和用途:

设置超时 - 将函数的执行延迟特定的持续时间。 - 不阻塞其余代码执行(异步行为) - 他们创建Macrotask(浏览器内部操作)

承诺 - 它们是允许异步执行代码的包装器(例如:ajax 调用)。 (不取决于具体的持续时间) - 它们对于链接不同的异步调用特别有用。 - 在您使用 await 运算符的情况下,不会阻止其余代码执行(异步行为)。 - 他们创建Microtask(浏览器内部操作),优先于Macrotask

推荐

当您想要将函数执行延迟某个特定的持续时间并且不阻塞进程中的其余代码执行时,请使用 setTimeout

使用承诺: 当您想执行一些异步代码并避免“回调地狱”时(是的,因为您可以在没有 Promises 的情况下进行异步 ajax 调用,但语法不太清晰且更容易出错)

【讨论】:

值得一提的是,Promise executor——传递给new Promise(...)的函数——同步运行。【参考方案6】:

这与Web Spec 中定义的事件循环 有关。浏览器有多个任务队列用于多种类型的任务(例如通过setTimeout创建的定时器任务),以及一个微任务队列(Promise 结算被推送到的地方) .每当浏览器执行完一个任务,它就会清空微任务队列并执行其中的所有任务,然后再继续执行另一个任务队列中的任务。

因此,在代码执行后(这是一个任务),Promise 结算在微任务队列中,而定时器任务可能已经在任务队列中¹。微任务队列被清空,Promise 解决。然后在定时器任务运行时。

¹ 浏览器可能会选择稍微增加超时时间,而且确实如此。在大多数浏览器中,超时将永远不会在 0 毫秒之后运行。

【讨论】:

以上是关于承诺与 setTimeout的主要内容,如果未能解决你的问题,请参考以下文章

与蓝鸟的承诺链阵列

承诺与异步与 Jsonwebtokens

承诺与 setTimeout

蓝鸟承诺 - 嵌套与拒绝模式

蓝鸟嵌套承诺与每个/传播

ES5 与 ES6 承诺