摘要:每次事件循环中,如果没有其他运行并且任务都执行完毕了,那么微任务就会在回调之后被执行。在微任务中排队的任何其他微任务将被添加到队列的末尾并进行处理。因此一个已的调用时将立即把一个任务加入任务队列。
原文地址(英):https://jakearchibald.com/201...
当我告诉Matt Gaunt(作者的同事),我正在谋划写一篇关于在浏览器事件循环(event loop)体系中微任务( microtask )的队列和执行的文章时,他说:“实话告诉你Jake,我对这篇文章是不会感兴趣的”。好吧,不管怎样,既然我已经写了那就让我们坐下来好好享受它,好吗?
事实上,如果视频更符合你的胃口,那么Philips Roberts 在JSConf上关于event loop的演讲会是很好的参考(该演讲不涉及微任务(microtask),但是对事件循环的其他部分都讲得非常好),闲话少说,开始我们的内容。
以下是一小段JavaScript: console.log("script start"); setTimeout(function() { console.log("setTimeout"); }, 0); Promise.resolve().then(function() { console.log("promise1"); }).then(function() { console.log("promise2"); }); console.log("script end");
想想看控制台会按照什么样的顺序打印结果呢?
正确的答案是:script start, script end, promise1, promise2, setTimeout,但是在不同的浏览器上结果可能会有所不同。
Microsoft Edge, Firefox 40, iOS Safari和桌面版Safari 8.0.8会在promise1,promise2之前打印出setTimeout,虽然这可能是浏览器厂商间各自竞争的结果,但这未免有些奇怪,因为Firefox 39和Safari 8.0.7得到的结果始终是正确的。
为什么会是这样为了搞清楚缘由,你需要明白事件循环(event loop)是如何处理任务(tasks)和微任务(microtasks)的.当这些名词第一次出现的时候,你可能会感到头疼,没关系,深呼吸...
每一个"线程"都拥有属于自己的事件循环(event loop),也就意味着每一个web worker都会存在自有的事件循环并独立运行互不干扰。然而所有同源窗口之间共享一个事件循环(event loop),这样它们就可以同步通信了(译者注:根据HTML5.2规范,事件循环分两种,一种是浏览器上下文的,一种是web worker的)。事件循环(event loop)总是不断的运行,执行队列中的任务(task)。一个事件循环存在多个任务源,这确保了任务在特定任务源的执行顺序(译者注:同一个任务源的任务将被添加到相同任务队列,不同任务源的任务可能被添加到不同任务队列),但是在每一次的循环中,浏览器会自主选择哪个源的任务优先执行,这确保了一些性能敏感的任务的优先级,比如用户输入。
任务(tasks,译者注:也叫macro-task)被放到任务源中,浏览器内部执行转移到JavaScript/DOM领域,并且确保这些 tasks按序执行。在tasks执行期间,浏览器可能更新渲染。来自鼠标点击的事件回调需要安排一个task,解析HTML和setTimeout同样需要。
setTimeout等待了给定的延迟时间之后就会为它的回调创建一个新的任务。这就是为什么setTimeout在script end之后打印script start,因为script end是归属于第一个任务,而setTimeout对应的是另一个任务,至此,我们快要搞清楚了,我需要你们有足够的耐心看完下一个部分
微任务(Microtasks)队列通常用于存放一些任务,这些任务应该在正在执行的脚本之后立即执行,比如对一批动作作出反应,或者操作异步执行避免创建整个新任务造成的性能浪费。每次事件循环中,如果没有其他JavaScript运行并且任务(task)都执行完毕了,那么微任务就会在回调之后被执行。在微任务中排队的任何其他微任务将被添加到队列的末尾并进行处理。微任务包括 MutationObserver、Promise的回调(译者注:微任务包括:process.nextTick(Nodejs), Promises, Object.observe, MutationObserver;任务(tasks)包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering)。
一个settled状态的promise(直接调用resolve或者reject)或者已经变成settled状态(异步请求被settled)的promise,会立刻将它的callback(then)放到microtask队列里面。这就能保证promise的回调是异步的,即便promise已经变为settled状态。因此一个已settled的promise调用.then(yey,nay)时将立即把一个microtask任务加入microtasks任务队列。这就是为什么 promise1 和 promise2 在 script end 之后打印,因为正在运行的代码必须在处理 microtasks 之前完成。promise1 和 promise2 在 setTimeout 之前打印,因为 microtasks 总是在下一个 task 之前执行。
让我们一步一步分析,(译者注:跳转到原文step by step示例,这对理解本文非常有用)
为什么在一些浏览器上的结果会有不同呢?一些浏览器打印出来的结果是:script start,script end,setTimeout,promise1,promise2。这些浏览器在promise回调之前调用了setTimeout。这很可能是浏览器把promise回调当做是新任务(task )的一部分而不是微任务(microtask)。
这种错误某种程度上是可以被原谅的,因为promises规范来源于ECMAScript而不是HTML。ECMAScript定义了类似微任务的“jobs”概念,但是除了一些模糊的邮件讨论之外,这种关系(jobs和microtasks)并不明确。但promises应该作为微任务的一部分这是普遍的共识。
把promise当做是任务将会导致一些性能问题,回调可能没有必要因为某些相关任务(比如渲染)而被延迟。由于与其他任务源的交互这也会导致一些不确定性,并且会中断与其他Api的交互。
把promise归类为微任务已经是很急迫的事情了。Webkit(Safari内核)一直都在做正确的事情,我想Safari最终会解决这和问题,事实上,Firefox43已经修复了这个问题。
真正有趣的是,Safari和Firefox在这里都经历了一次回归,从那以后问题就被修复了。我想知道这是不是一个巧合。
译者注task -> microtask -> ui render
对于promise而言,决议后(resolve或reject)才会把then回调推入microtaks队列
参考从Promise来看JavaScript中的Event Loop、Tasks和Microtasks
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/107343.html
摘要:在微任务期间排队的任何其他微任务都会被添加到队列的末尾并进行处理。因此一个已的调用时将立即把一个微任务加入微任务队列中。和回调被列为微任务。上述规则确保微任务不会中断执行中期的。 为了保证的可读性,本文采用意译而非直译。 想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你! 思考下面 JavaScript 代码: console.log(script start); ...
摘要:而这些队列由的事件循环来搞定宏任务与微任务,在最新标准中,它们被分别称为与。我们梳理一下事件循环的执行机制循环首先从宏任务开始,遇到,生成执行上下文,开始进入执行栈,可执行代码入栈,依次执行代码,调用完成出栈。 写在前面 js是一门单线程的编程语言,也就是说js在处理任务的时候,所有任务只能在一个线程上排队被执行,那如果某一个任务耗时比较长呢?总不能等到它执行结束再去执行下一个。所以在...
摘要:事件循环事件循环具有至少两个队列处理任务。同时含有宏任务和微任务主线程运行运行运行运行本例中在的事件处理函数里增加了一个立即兑现的,需要运行。处理的事件处理函数计时器主线程运行运行运行运行 一直对js的事件循环不是很清晰,最近看了JavaScript忍者秘籍的第13章后,有了一些感悟,特此总结一下,分享给大家。 单线程 众所周知,JavaScript是单线程执行模型,同一时刻只能执行一...
阅读 1262·2021-09-22 15:09
阅读 2588·2021-08-20 09:38
阅读 2385·2021-08-03 14:03
阅读 841·2019-08-30 15:55
阅读 3353·2019-08-30 12:59
阅读 3531·2019-08-26 13:48
阅读 1868·2019-08-26 11:40
阅读 612·2019-08-26 10:30