-
Notifications
You must be signed in to change notification settings - Fork 211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
从Promise来看JavaScript中的Event Loop、Tasks和Microtasks #21
Comments
有一个问题就是,I/O, UI render 究竟是不是当做一个task来执行,看有的文章说是,有的说是在每个task之间进行的 |
@cendylee 就我看到的资料来说,好像是在task之间进行的。如果有更详细可靠的资料进行补充或更正当然更好。 |
Promise的then原型方法注册的回调确实是在microtask中注册执行的,但是我很好奇,node实现的process.nextTick,看源码似乎并不是由microtask驱动的,为啥网上到处都说process.nextTick也是属于microtask的一部分呢? |
@hyj1991 从源码来看, https://github.com/nodejs/node/blob/v7.x/src/node.cc#L4381 inline int Start(Isolate* isolate, IsolateData* isolate_data,
int argc, const char* const* argv,
int exec_argc, const char* const* exec_argv) {
...
{
Environment::AsyncCallbackScope callback_scope(&env);
LoadEnvironment(&env);
} https://github.com/nodejs/node/blob/v7.x/src/node.cc#L3406 void LoadEnvironment(Environment* env) {
...
Local<String> script_name = FIXED_ONE_BYTE_STRING(env->isolate(),
"bootstrap_node.js");
Local<Value> f_value = ExecuteString(env, MainSource(env), script_name); https://github.com/nodejs/node/blob/v7.x/lib/internal/bootstrap_node.js#L12 function startup() {
...
NativeModule.require('internal/process/next_tick').setup(); https://github.com/nodejs/node/blob/v7.x/lib/internal/process/next_tick.js#L49 function scheduleMicrotasks() {
if (microtasksScheduled)
return;
nextTickQueue.push({
callback: runMicrotasksCallback,
domain: null
});
tickInfo[kLength]++;
microtasksScheduled = true;
}
...
function nextTick(callback) {
if (typeof callback !== 'function')
throw new TypeError('callback is not a function');
// on the way out, don't bother. it won't get fired anyway.
if (process._exiting)
return;
var args;
if (arguments.length > 1) {
args = new Array(arguments.length - 1);
for (var i = 1; i < arguments.length; i++)
args[i - 1] = arguments[i];
}
nextTickQueue.push({
callback,
domain: process.domain || null,
args
});
tickInfo[kLength]++;
} 可以看到,安排microtask就是向 20170629 更新代码路径 |
很清晰的文章! |
@Ma63d 你给的链接里描述很清晰,microtask跟你说的这些无关。 链接描述,一个event loop的典型步骤:
Update the rendering是event loop的一个步骤,Microtasks也是event loop的一个步骤,在执行完Microtasks,浏览器开始Update the rendering,至于 |
@creeperyang 嗯,我当时的表示有误,我问的意思是执行完所有的microtask之后执行的Update the rendering,不是说单独执行一个之后立马执行run the resize steps/run the scroll steps。我当时描述不准确,不好意思把你引入到了莫名的关注点上面去了。 我当时一开始没看懂,为什么Update the rendering竟然去执行resize窗口和scroll窗口。不可能每次task执行完,然后清空完microtask队列之后就让屏幕resize/scroll一次吧。所以评论了一开始的内容。 最近仔细看了一下run the scroll steps不是scoll窗口,每次我们scoll的时候视口或者dom就已经立即scroll了,并把document或者dom加入到 pending scroll event targets中,而run the scroll steps具体做的则是遍历这些target,在target上触发scroll事件。 至于后续的media query, run CSS animations and send events等等也是相似的,都是触发事件,第10步和第11步则是执行我们熟悉的requestAnimationFrame回调和IntersectionObserver回调(第十步还是挺关键的)。 第十二步才是对UI执行render,这里应该就是重排、重绘和把更改后的样式真正render改到dom上面去。 |
SetTimeout 内的回调属于 macrotask, 会在下一个 Event Loop 中执行 |
关于第2,3步,不是promise.resolve()的时候,promise.then的callback已经被添加到microtask queue了吗?然后输出2,然后跳到第四步输出3. |
为了让第2,3步更清晰一点,可以写成下面这样。 // 1
setTimeout();
// 2
var promise = new Promise(executor);
// 3
promise.then(callback)
// 4
console.log(3) 其中,得到 构造函数执行完后,我们得到了 调用
|
@mqliutie |
学习学习 |
应该是每个浏览器至少有一个 event loop 吧 |
补充关于 同步/异步/阻塞/非阻塞 的理解。搬迁自已废弃的#15,因为觉得和 Synchronize / Asynchronize / Block / Non-block 一个从分布式系统角度的理解这一段主要来自 知乎 怎样理解阻塞非阻塞与同步异步的区别? 严肃的答案 ,并参照了 stackoverflow 的相关问题。 同步与异步,阻塞与非阻塞是两组概念,但容易混淆,比如同步不代表阻塞,同步也可以是非阻塞的。 同步与异步
阻塞与非阻塞
举例以你打电话让书店老板查找某本书为例来讲:
总结:阻塞与非阻塞 与 是否同步异步无关(跟老板通过什么方式回答你无关)。 所以,也可以说, 同步/异步 针对的是 通信机制(被调用方怎么通知调用方),阻塞/非阻塞 针对的是 调用方在等待结果时的状态。 此外,同步/异步 和 阻塞/非阻塞 可以相互组合 (from 吴昌明 的评论):
这个回答我觉得是很清晰,易于理解的解释,不过下面还是会列出一些其它角度的解释,方便对照吧。 Asynchronous vs synchronous execution, what does it really mean?这是关于同步/异步执行的理解,几个高票答案总结下可知:
这也是一个角度,可参照。 |
是否可以这样认为,同步异步是两者的通信方式,CPU 同 I/O 或数据库。阻塞非阻塞是个体的工作方式,进程的单线程、多线程。单线程工作方式的进程就是阻塞的,多线程工作方式的进程就是非阻塞的。 NodeJS 应该是利用了事件循环既实现了两者间的异步通信,又解决了单线程的阻塞难题。(对比时分复用,将单线程按事件划分成虚拟的多线程,但是对CPU密集程序来说它还是阻塞的) |
@szouc 阻塞/非阻塞和单/多线程没有对应关系,同步异步和线程也没有关系。 在Node.js中,JavaScript是单线程的,所以必然不能使用阻塞IO(阻塞就没法实现高并发)。
通过libuv,Node.js 在多平台支持了非阻塞IO:
|
请教博主一个问题,烦请指教!当我反复查看html规范时候
规范里面说到event loop有多个task queues.一个task queue就是任务的列表,它们负责以下工作。 |
new Promise(resolve => {
resolve(1);
console.log(4)
// new Promise(r => r(2)).then(e => console.log(e))
Promise.resolve(2).then(e => console.log(e))
Promise.resolve(5).then(e => console.log(e))
}).then(v => console.log(v))
console.log(3) 在 Promise 中, 如果还有 promise, 那么先得把内部的 promise 顺序执行完,然后执行外面的 promise? 我是根据得到的结果推理出来的, 暂时没看到对应的文档说明, issue 主怎么看? |
// 执行第 1 步
const p = new Promise(resolve => {
resolve(1);
// 第 1.1 步,首先输出 4
console.log(4)
// 第 1.2 步,压入microtask 队列
Promise.resolve(2).then(e => console.log(e))
// 第 1.3 步,压入microtask 队列
Promise.resolve(5).then(e => console.log(e))
})
// 执行第 2 步,压入microtask 队列
p.then(v => console.log(v))
// 执行第 3 步
console.log(3) 所以输出结果:
|
@creeperyang 步骤分解之后是更清楚一点。 我之前的疑问是, 为什么 1.2, 1.3 先于步骤2,只是因为 1.2 1.3 在构造器中, 先执行的吧 |
一篇从浏览器进程/线程角度来解释浏览器环境中JS运行机制的文章,通俗易懂,对理解浏览器环境的 event loop 很有帮助。 核心知识点:
|
我最近正好看到这个地方,颇有疑惑之处,首先,node的makecallback 有调用 env()->tick_callback_function()->Call;所以我刚开始感觉nextTick是在事件循环的回调中调用。但是实际上比如用setTimeout(fn,0)这个是标准的libuv timer然后回调执行fn,那应该是在执行完fn以后再执行nexttick,但实际不是,所以我比较同意是Microtasks来执行的,而且应该不是在同一个线程上,因为node启动platform的时候用了4个线程做线程池,然后再node::start里uv_run前面有个PumpMessageLoop更加重了我的猜测,就是这个 是microtasks来做的nexttick。而且如果我通过执行自己的embed函数 在函数中直接运行uv_run,不通过node::start里面的uv_run执行,nextTick是不会被执行的。 |
楼主能说下 tick和loop是啥区别吗? |
342561 |
楼主好,根据最近一次的新闻,WHATWG全权接管了HTML和DOM的标准制定权(链接)。 |
终于解答了最近被一小段代码的困扰:
在低版本Node中,输出结果如上,而高版本我用的V12,则6会在after 3后面输出。 但是规范里并没有说,多个Job Queue如何调度(可能有交叉调度),这也导致没法正确推导代码输出结果。哪位有了解多个Job Queue的调度方式,欢迎告知或提供学习资料,感谢! |
浏览器 event loop 简易归纳:关于 taskQueue
关于运行流程(已简化)
一些重要的执行顺序
一个有意思的实例
|
@spademan 我实际测了下,直接 可以看到, 我的猜测是:用户点击,是属于用户交互,浏览器认为在UI上需要立即反馈,于是每处理完一个handler,立即更新页面UI。(看看就行,别当真 😸 有官方解释可告知我) |
@spademan 用户点击,事件是异步触发,点击之后
用程序触发 click, 事件是同步触发
不晓得对不对。。。。 |
|
看到过下面这样一道题:
为什么输出结果是
1,2,3,5,4
而非1,2,3,4,5
?比较难回答,但我们可以首先说一说可以从输出结果反推出的结论:
Promise.then
是异步执行的,而创建Promise实例(executor
)是同步执行的。setTimeout
的异步和Promise.then
的异步看起来 “不太一样” ——至少是不在同一个队列中。相关规范摘录
在解答问题前,我们必须先去了解相关的知识。(这部分相当枯燥,想看结论的同学可以跳到最后即可。)
Promise/A+
规范要想找到原因,最自然的做法就是去看规范。我们首先去看看Promise的规范。
摘录
promise.then
相关的部分如下:规范要求,
onFulfilled
必须在 执行上下文栈(execution context stack) 只包含 平台代码(platform code) 后才能执行。平台代码指 引擎,环境,Promise实现代码。实践上来说,这个要求保证了onFulfilled
的异步执行(以全新的栈),在then
被调用的这个事件循环之后。规范的实现可以通过 macro-task 机制,比如
setTimeout
和setImmediate
,或者 micro-task 机制,比如MutationObserver
或者process.nextTick
。因为promise的实现被认为是平台代码,所以可以自己包涵一个task-scheduling
队列或者trampoline
。通过对规范的翻译和解读,我们可以确定的是
promise.then
是异步的,但它的实现又是平台相关的。要继续解答我们的疑问,必须理解下面几个概念:Event Loop
规范HTML5规范里有Event loops这一章节(读起来比较晦涩,只关注相关部分即可)。
Events
task,Parsing
task,Callbacks
task,Using a resource
task,Reacting to DOM manipulation
task等。每个task都有自己相关的document,比如一个task在某个element的上下文中进入队列,那么它的document就是这个element的document。
每个task定义时都有一个task source,从同一个task source来的task必须放到同一个task queue,从不同源来的则被添加到不同队列。
每个(task source对应的)task queue都保证自己队列的先进先出的执行顺序,但event loop的每个turn,是由浏览器决定从哪个task source挑选task。这允许浏览器为不同的task source设置不同的优先级,比如为用户交互设置更高优先级来使用户感觉流畅。
Jobs and Job Queues
规范本来应该接着上面Event Loop的话题继续深入,讲macro-task和micro-task,但先不急,我们跳到ES2015规范,看看
Jobs and Job Queues
这一新增的概念,它有点类似于上面提到的task queue
。一个
Job Queue
是一个先进先出的队列。一个ECMAScript实现必须至少包含以下两个Job Queue
:单个
Job Queue
中的PendingJob总是按序(先进先出)执行,但多个Job Queue
可能会交错执行。跟随PromiseJobs到25.4章节,可以看到PerformPromiseThen ( promise, onFulfilled, onRejected, resultCapability ):
这里我们看到,
promise.then
的执行其实是向PromiseJobs
添加Job。event loop怎么处理tasks和microtasks?
好了,现在可以让我们真正来深入task(macro-task)和micro-task。
认真说,规范并没有包括macro-task 和 micro-task这部分概念的描述,但阅读一些大神的博文以及从规范相关概念推测,以下所提到的在我看来,是合理的解释。但是请看文章的同学辩证和批判地看。
首先,micro-task在ES2015规范中称为Job。 其次,macro-task代指task。
哇,所以我们可以结合前面的规范,来讲一讲Event Loop(事件循环)是怎么来处理task和microtask的了。
mutation observer callbacks
和promise callbacks
。结论
定位到开头的题目,流程如下:
setTimeout
的callback被添加到tasks queue中;1
; promise resolved;输出2
;promise.then
的callback被添加到microtasks queue中;3
;5
;4
。The text was updated successfully, but these errors were encountered: