浏览器
child::
浏览器 事件循环
梗概:
不同的代码会按照功能被划分 并按照以下顺序先后执行(执行完一个才能执行下一个):
实际应用
- 减少js同步执行耗时,因为它会阻塞页面的渲染
- 把轮询改成异步
实例
之一:
async function promise1() { console.log('promise1') } async function sleep(delay, msg) { return new Promise((resolve, reject) => { console.log(msg) setTimeout(() => { resolve() }, delay) }) } async function fun1() { console.log('fun1 start'); await promise1(); console.log(`micro1`) await sleep(1000, 'sleep1') console.log('micro2') await sleep(0, 'sleep2') console.log('micro3') } console.log('script start') fun1() setTimeout(() => { console.log('timeout1') }, 1500); setTimeout(() => { console.log('timeout2') }, 0); console.log('script end')输出
script start fun1 start promise1 script end micro1 sleep1 timeout2 micro2 sleep2 micro3 timeout1解释
- 首先执行当前任务栈,输出
script start、fun1 start、promise1- await立马完成,后面的所有代码都被加入微任务队列
- 跳出fun1,继续往下执行,添加了两个宏任务,并最后输出
script end- 当前任务所有执行完毕,从微任务中取出所有任务执行,输出
micro1,sleep1,并把sleep1后面的所有代码都跳过- 执行完微任务,从宏任务中取一个已完成的,即输出
timeout2- 执行了一个宏任务,查看微任务队列,为空
- 等待
- sleep1微任务最先完成,之后新加入微任务立马执行,所以输出
micro2,sleep2,立马完成加入新的微任务,所以继续输出micro3- 接下来最后到达的是timeout1,输出
timeout1之一
async function promise1() { console.log('promise1') } async function sleep(delay, msg) { return new Promise((resolve, reject) => { console.log(msg) setTimeout(() => { resolve() }, delay) }) } async function fun1() { console.log('fun1 start'); await promise1(); console.log(`micro1`) await sleep(1000, 'sleep1') console.log('micro2') await sleep(0, 'sleep2') console.log('micro3') } (async function main() { console.log('script start') await fun1() setTimeout(() => { console.log('timeout1') }, 1500); setTimeout(() => { console.log('timeout2') }, 0); console.log('script end') })()输出
script start fun1 start promise1 micro1 sleep1 micro2 sleep2 micro3 script end timeout2 timeout1解释
指向原始笔记的链接
script start、fun1 start、promise1,promise1马上完成,加入后面的所有代码为微任务- 跳过fun1后的所有代码
- 执行所有的微任务,输出
micro1,sleep1,跳过后面的代码- 等待
- sleep1完成,加入后面的代码为微任务
- 执行所有的微任务,输出
micro2,sleep2,继续执行新加入的微任务,输出micro3- fun1完成,加入后面的代码为微任务
- 执行所有的微任务,加入了两个宏任务,然后输出
script end- 拿一个宏任务执行,输出
timeout2- 等待
- 没有微任务,拿一个宏任务执行,输出
timeout1
nodejs
child::
nodejs事件循环
梗概:
- nodejs的异步操作比较多,去除nodejs特有的异步操作,剩下异步的执行顺序与浏览器规范靠拢
- Node在10的某个版本将循环机制改成和浏览器一致了
- nodejs将事件循环中宏任务分为6个步骤:
- 宏任务分为6个步骤:Timers(定时器)、PendingCallbacks(操作系统相关的)、idle,prepare(nodejs内部)、Poll(IO操作)、Check(setimmediate)、ClosingCallBacks(关闭事件)
- 微任务分为3个优先级:nextTick的回调、promise的回调、setImmediate
- 每个宏任务之间都要执行所有的微任务
图解
宏任务讲解
定时器(Timer)阶段
这个是事件循环开始的阶段,绑定到这个阶段的队列,保留着定时器(setTimeout, setInterval)的回调。尽管它并没有将回调推入队列中,但是用最小堆来存储计时器并且在到达规定的时间后执行回调。
即将发生的(Pending) i/o 回调阶段
这个阶段执行在事件循环中
pending_queue里的回调。这些回调是被之前的操作推入的。例如当你尝试往 tcp 中写入一些东西,这个工作完成了,然后回调被推入到队列中。错误处理的回调也在这里。Idle, Prepare 阶段
尽管名字是空闲(idle),但是每个循环(tick)都运行。Prepare 也在轮询阶段开始之前运行。不管怎样,这两个阶段是 node 主要做一些内部操作的阶段;因此,我们不在这儿讨论。
轮询(Poll)阶段
可能整个事件循环最重要的一个阶段就是
poll phase。这个阶段接受新传入的连接(新的 Socket 建立等)和数据(文件读取等)。我们可以将轮询阶段分成几个不同的部分。
- 如果
watch_queue(这个队列被绑定到轮询阶段)不为空,它们将会被一个接着一个的执行直到队列为空或者系统到达最大的限制。- 一旦队列为空,node 就会等待新的连接。等待或者睡眠的时间取决于多种因素,待会儿我们会讨论。
检查(Check)阶段
轮询的下一个阶段是
check phase,这个专用于setImmediate的阶段。为什么需要一个专门的队列来处理 setImmediate 回调。这是因为轮询阶段的行为,待会儿将在流程部分讨论。现在只需要记住检查(check)阶段主要用于处理 setImmediate() 的回调。关闭(Close)回调
回调的关闭(socket.on(‘close’, ()⇒{})) 都在这里处理的,更像一个清理阶段
示例
// Execute this function at the end of the next tick setTimeout(() => { console.log('timeout1'); }, 0); setTimeout(() => { console.log('timeout2'); }, 0); // Execute this function after the current tick setImmediate(() => { console.log('immediate1'); }); // Execute this function just before the next tick process.nextTick(() => { console.log('nextTick1'); }); function promise() { return new Promise<void>((resolve, reject) => { resolve(); }) } promise().then(() => { console.log('promise1'); })执行结果
指向原始笔记的链接 nextTick1 promise1 timeout1 timeout2 immediate1
