Skip to content
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

第 25 题:浏览器和Node 事件循环的区别 #26

Closed
Rain120 opened this issue Feb 28, 2019 · 17 comments
Closed

第 25 题:浏览器和Node 事件循环的区别 #26

Rain120 opened this issue Feb 28, 2019 · 17 comments

Comments

@Rain120
Copy link

Rain120 commented Feb 28, 2019

No description provided.

@nomoreyou
Copy link

nomoreyou commented Mar 1, 2019

其中一个主要的区别在于浏览器的event loop 和nodejs的event loop 在处理异步事件的顺序是不同的,nodejs中有micro event;其中Promise属于micro event 该异步事件的处理顺序就和浏览器不同.nodejs V11.0以上 这两者之间的顺序就相同了.
参考一下系列文章:https://jsblog.insiderattack.net/new-changes-to-timers-and-microtasks-from-node-v11-0-0-and-above-68d112743eb3

@Liubasara
Copy link

为楼上补充一个例子

原文出自liubasara的个人博客

function test () {
   console.log('start')
    setTimeout(() => {
        console.log('children2')
        Promise.resolve().then(() => {console.log('children2-1')})
    }, 0)
    setTimeout(() => {
        console.log('children3')
        Promise.resolve().then(() => {console.log('children3-1')})
    }, 0)
    Promise.resolve().then(() => {console.log('children1')})
    console.log('end') 
}

test()

// 以上代码在node11以下版本的执行结果(先执行所有的宏任务,再执行微任务)
// start
// end
// children1
// children2
// children3
// children2-1
// children3-1

// 以上代码在node11及浏览器的执行结果(顺序执行宏任务和微任务)
// start
// end
// children1
// children2
// children2-1
// children3
// children3-1

@fengT-T
Copy link

fengT-T commented Mar 4, 2019

题目应该是:浏览器和node的事件循环的区别吧,

先上链接:

第一个链接里面大佬讲的已经非常透彻了我来总结一下。

浏览器

关于微任务和宏任务在浏览器的执行顺序是这样的:

  • 执行一只task(宏任务)
  • 执行完micro-task队列 (微任务)

如此循环往复下去

浏览器的task(宏任务)执行顺序在 html#event-loops 里面有讲就不翻译了
常见的 task(宏任务) 比如:setTimeout、setInterval、script(整体代码)、 I/O 操作、UI 渲染等。
常见的 micro-task 比如: new Promise().then(回调)、MutationObserver(html5新特性) 等。

Node

Node的事件循环是libuv实现的,引用一张官网的图:

default

大体的task(宏任务)执行顺序是这样的:

  • timers定时器:本阶段执行已经安排的 setTimeout() 和 setInterval() 的回调函数。
  • pending callbacks待定回调:执行延迟到下一个循环迭代的 I/O 回调。
  • idle, prepare:仅系统内部使用。
  • poll 轮询:检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,它们由计时器和 setImmediate() 排定的之外),其余情况 node 将在此处阻塞。
  • check 检测:setImmediate() 回调函数在这里执行。
  • close callbacks 关闭的回调函数:一些准备关闭的回调函数,如:socket.on('close', ...)。

微任务和宏任务在Node的执行顺序

Node 10以前:

  • 执行完一个阶段的所有任务
  • 执行完nextTick队列里面的内容
  • 然后执行完微任务队列的内容

Node 11以后:
和浏览器的行为统一了,都是每执行一个宏任务就执行完微任务队列。

@yygmind yygmind changed the title JavaScript 事件循环和Node 事件循环的区别 第25题:浏览器和Node 事件循环的区别 Mar 4, 2019
@XueSeason
Copy link

Node 官方文档从始至终到没有提到微任务和宏任务的概念。

之所以会出现 Node 10 和 Node 11 的异步执行差异,可以看官方文档 Deduplication 这一节内容,以及 Node 11 的 PR,TLDR 在 Node 11 之后是为了消除和浏览器执行的差异。

@yu910709
Copy link

yu910709 commented Apr 2, 2019

Promise.resolve().then(() => {console.log('children2-1')})

大佬,我的理解是:在11之前宏任务中有微任务的,会把微任务仍到微任务的队列,先执行了宏任务中的立即执行方法再去执行微任务。11以后执行宏任务的时候会把自己任务中所有的事儿(除了宏任务)都干完才会执行下个阶段任务,而不会将其中的微任务搁置到微任务队列。这么理解对么。。。

@yygmind yygmind changed the title 第25题:浏览器和Node 事件循环的区别 第 25 题:浏览器和Node 事件循环的区别 Apr 26, 2019
@yingye
Copy link

yingye commented Jun 6, 2019

https://juejin.im/post/5c337ae06fb9a049bc4cd218

@zhoushoujian
Copy link

nodejs的api事件循环基本都是io多线程异步,而浏览器除了ajax外基本都是非阻塞异步模型

@xufeiayang
Copy link

为楼上补充一个例子

原文出自liubasara的个人博客

function test () {
   console.log('start')
    setTimeout(() => {
        console.log('children2')
        Promise.resolve().then(() => {console.log('children2-1')})
    }, 0)
    setTimeout(() => {
        console.log('children3')
        Promise.resolve().then(() => {console.log('children3-1')})
    }, 0)
    Promise.resolve().then(() => {console.log('children1')})
    console.log('end') 
}

test()

// 以上代码在node11以下版本的执行结果(先执行所有的宏任务,再执行微任务)
// start
// end
// children1
// children2
// children3
// children2-1
// children3-1

// 以上代码在node11及浏览器的执行结果(顺序执行宏任务和微任务)
// start
// end
// children1
// children2
// children2-1
// children3
// children3-1

实际测试,不管是不是node11还是11以下都是下面的结果
start
end
children1
children2
children2-1
children3
children3-1

@xufeiayang
Copy link

function sleep(time) {    
	let startTime = new Date();    
	while (new Date() - startTime < time) {}    
	console.log('<--Next Loop-->');
}

setTimeout(() => {    
	console.log('timeout1');
    setTimeout(() => {        
    	console.log('timeout3');
        sleep(1000);
    });    
    new Promise((resolve) => {        
    	console.log('timeout1_promise');
        resolve();
    }).then(() => {        
    	console.log('timeout1_then');
    });
    sleep(1000);
});
     
setTimeout(() => {    
	console.log('timeout2');
    setTimeout(() => {        
    	console.log('timeout4');
        sleep(1000);
    });    
    new Promise((resolve) => {        
    	console.log('timeout2_promise');
        resolve();
    }).then(() => {       
    	console.log('timeout2_then');
    });
    sleep(1000);
});

浏览器下的结果
image

node下结果
timeout1
timeout1_promise
<--Next Loop-->
timeout2
timeout2_promise
<--Next Loop-->
timeout1_then
timeout2_then
timeout3
<--Next Loop-->
timeout4
<--Next Loop-->

@mongonice
Copy link

mongonice commented Jul 17, 2019

浏览器的EventLoop是将所有的微任务执行完,再执行宏任务,再执行宏任务中所有的微任务,再执行宏任务,再执行宏任务中的所有微任务

node的时间循环是交叉执行,执行完同级的所有timer类, 再执行同级的所有promise.then,再执行所有的同级timer

function sleep(time) {    
	let startTime = new Date();    
	while (new Date() - startTime < time) {}    
	console.log('<--Next Loop-->');
}

宏任务1-setTimeout(() => {    
	console.log('宏任务timeout1-一级');
    宏任务3-setTimeout(() => {        
    	console.log('宏任务timeout1-二级');
        sleep(1000);
    });    
    new Promise((resolve) => {        
    	console.log('微任务promise1-同步');
        resolve();
    }).then(() => {        
    	console.log('微任务promise1-异步then');
    });
    sleep(1000);
});
     
宏任务2-setTimeout(() => {    
	console.log('宏任务timeout2-一级');
    宏任务4-setTimeout(() => {        
    	console.log('宏任务timeout2-二级');
        sleep(1000);
    });    
     new Promise((resolve) => {        
    	console.log('微任务promise2-同步');
        resolve();
    }).then(() => {       
    	console.log('微任务promise2-异步then');
    });
    sleep(1000);
});

浏览器执行结果:

  • 主程序中 两个宏任务,那就先执行 宏任务1 里的同步代码
    输出:
    宏任务timeout1-一级
    微任务promise1-同步
    <--Next Loop-->
    再执行宏任务1中的微任务promise.then代码
    输出:
    微任务promise1-异步then
    第一轮EventLoop结束!!!

  • 第二轮宏任务2开始执行同步代码:
    输出:
    宏任务timeout2-一级
    微任务promise2-同步
    <--Next Loop-->
    再执行宏任务2中的微任务promise.then代码
    输出:
    微任务promise2-异步then
    第二轮EventLoop结束!!!

  • 第三轮宏任务3开始执行同步代码,没有微任务代码
    输出:
    宏任务timeout1-二级
    <--Next Loop-->
    第三轮EventLoop结束!!!

  • 第四轮宏任务4开始执行同步代码,没有微任务代码
    输出:
    宏任务timeout2-二级
    <--Next Loop-->
    第四轮EventLoop结合!!!

// 浏览器输出结果
宏任务timeout1-一级
微任务promise1-同步
<--Next Loop-->
微任务promise1-异步then
宏任务timeout2-一级
微任务promise2-同步
<--Next Loop-->
宏任务timeout1-二级
<--Next Loop-->
宏任务timeout2-二级
<--Next Loop-->

node执行结果:

  • 先同级 宏任务timeout1 和 宏任务timeout2
    执行timeout1同步代码输出:
    宏任务timeout1-一级
    微任务promise1-同步
    <--Next Loop-->
    执行timeout2同步代码输出:
    宏任务timeout2-一级
    微任务promise2-同步
    <--Next Loop-->

  • 再执行同级 微任务promise1 和 promise2
    执行promise1代码输出:
    微任务promise1-异步then
    执行promise2代码输出:
    微任务promise2-异步then

  • 再执行同为2级的宏任务timeout3 和 timeout4
    执行timeout3输出:
    宏任务timeout1-二级
    <--Next Loop-->
    执行timeout4输出:
    宏任务timeout2-二级
    <--Next Loop-->

// node11以下执行
宏任务timeout1-一级
微任务promise1-同步
<--Next Loop-->
宏任务timeout2-一级
微任务promise2-同步
<--Next Loop-->
微任务promise1-异步then
微任务promise2-异步then
宏任务timeout1-二级
<--Next Loop-->
宏任务timeout2-二级
<--Next Loop-->

@kukudeshiyi
Copy link

差异体现在nodeV10之前
浏览器是执行完一个宏任务就会去清空微任务队列;node则是将同源的宏任务队列执行完毕后再去清空微任务队列;
另外,宏任务内若嵌套同源宏任务,仍会放进一个队列,但是执行将会放在下一次事件循环;(举个例子,timeoutTwo中包含一个timeoutThree,timeoutThree仍会放进setTimeout队列,但并不会与one、two一起执行完毕,而是等到清空微任务队列的下一次循环时执行);
image

例子:

console.log(1);

setTimeout(() => {
    console.log(2)
    new Promise((resolve) => {
        console.log(6);
        resolve(7);
    }).then((num) => {
        console.log(num);
    })
});

setTimeout(() => {
    console.log(3);
       new Promise((resolve) => {
        console.log(9);
        resolve(10);
    }).then((num) => {
        console.log(num);
    })
    setTimeout(()=>{
    	console.log(8);
    })
})

new Promise((resolve) => {
    console.log(4);
    resolve(5)
}).then((num) => {
    console.log(num);
    new Promise((resolve)=>{
    	console.log(11);
    	resolve(12);
    }).then((num)=>{
    	console.log(num);
    })
})

答案:
左边为浏览器的执行结果,右边为node v8.12.0的执行结果
image

@dmljc
Copy link

dmljc commented Oct 24, 2019

@Liubasara Promise.resolve().then(() => {console.log('children1')}) 这个是微任务吧????怎么能说: 以上代码在node11以下版本的执行结果(先执行所有的宏任务,再执行微任务)?????请指教??

@helloforrestworld
Copy link

JavaScript 事件循环和Node 事件循环

@ShallowGreen
Copy link

为楼上补充一个例子

原文出自liubasara的个人博客

function test () {
   console.log('start')
    setTimeout(() => {
        console.log('children2')
        Promise.resolve().then(() => {console.log('children2-1')})
    }, 0)
    setTimeout(() => {
        console.log('children3')
        Promise.resolve().then(() => {console.log('children3-1')})
    }, 0)
    Promise.resolve().then(() => {console.log('children1')})
    console.log('end') 
}

test()

// 以上代码在node11以下版本的执行结果(先执行所有的宏任务,再执行微任务)
// start
// end
// children1
// children2
// children3
// children2-1
// children3-1

// 以上代码在node11及浏览器的执行结果(顺序执行宏任务和微任务)
// start
// end
// children1
// children2
// children2-1
// children3
// children3-1

实际测试,不管是不是node11还是11以下都是下面的结果
start
end
children1
children2
children2-1
children3
children3-1

用n切版本的时候,刚切换完跑确实是这样的,我执行了一下node -v,显示确实切换成功了,再跑一次就变了

@dsb123dsb
Copy link

参考这篇文章浏览器与Node的事件循环(Event Loop)有何区别?,我有一个疑问

const fs = require('fs')
fs.readFile(__filename, () => {
    setTimeout(() => {
        console.log('timeout');
    }, 0)
    setImmediate(() => {
        console.log('immediate')
    })
})

在上述代码中,setImmediate 永远先执行。因为两个代码写在 IO 回调中,IO 回调是在 poll 阶段执行,当回调执行完毕后队列为空,发现存在 setImmediate 回调,所以就直接跳转到 check 阶段去执行回调了。

而下面这段代码的先后顺序就不确定了。疑问就在这里,按照推断,这部分的代码应该也是运行在poll阶段的,为什么他们顺序不确定?看了node官方文档也没有弄清楚,望哪位大神解惑。

setTimeout(function timeout () {
  console.log('timeout');
},0);
setImmediate(function immediate () {
  console.log('immediate');
});

参考这篇文章浏览器与Node的事件循环(Event Loop)有何区别?,我有一个疑问

const fs = require('fs')
fs.readFile(__filename, () => {
    setTimeout(() => {
        console.log('timeout');
    }, 0)
    setImmediate(() => {
        console.log('immediate')
    })
})

在上述代码中,setImmediate 永远先执行。因为两个代码写在 IO 回调中,IO 回调是在 poll 阶段执行,当回调执行完毕后队列为空,发现存在 setImmediate 回调,所以就直接跳转到 check 阶段去执行回调了。

而下面这段代码的先后顺序就不确定了。疑问就在这里,按照推断,这部分的代码应该也是运行在poll阶段的,为什么他们顺序不确定?看了node官方文档也没有弄清楚,望哪位大神解惑。

setTimeout(function timeout () {
  console.log('timeout');
},0);
setImmediate(function immediate () {
  console.log('immediate');
});

文章解释挺清楚了,setTimeout设置0回调其实并不能精确到0的,时间在0-1之间

这是由源码决定的 进入事件循环也是需要成本的,如果在准备时候花费了大于 setTimeout回调的时间,那么在 timer 阶段就会直接执行 setTimeout 回调
如果准备时间花费小于,那么就是 setImmediate 回调先执行了

@SoftwareEngineerPalace
Copy link

和浏览器的行为统一了,都是每执行一个宏任务就执行完微任务队列

所以没区别了是吗

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests