eventLoop整理1

128 阅读6分钟
/*
1.队列
队列的数据又称为队列元素,在队列插入元素称为入队,删除元素称为出队,队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,所以队列是先进先出

2.eventLoop
JS stack 
在js中任务被分为两种红任务macrotask也叫task,一种叫微任务microTask
MacroTask(宏任务): script的全部代码,setTimout,setInterval setTmmediate(浏览器暂时不支持) I/O UIRendering
MicroTask(微任务):Process.nextTick(node独有)Promise Object.observer(废弃) MutationObserver(具体https://javascript.ruanyifeng.com/dom/mutationobserver.html)

3.浏览器的eventLoop
JavaScript有一个main thread 主线程和 call-stack调用栈(执行栈) 所有的任务都会放在调用栈中等主线程执行

3.1 js调用栈
JS调用栈采用的是后进先出的规则,当函数执行的时候,会被添加到栈的顶部,当执行栈执行完成后,就会从栈顶移出,直到栈内被清空。
3.2 同步任务和异步任务
Javascript单线程任务被分为同步任务和异步任务,同步任务会在调用栈中按照顺序等待主线程依次执行,异步任务会在异步任务有了结果后,将注册的回调函数放入任务队列等待主线程中空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行
(任务队列1图)
3.3 事件循环的进程模型
*选择当前要执行的任务队列,选择任务队列最先进入的任务,如果认为队列为空即null,则执行跳转到微任务microTask执行步骤
*将事件循环中的任务设置为已选择任务
*执行任务
*将事件循环中当前运行任务设置为null
*将已经运行完成的任务从任务队列中删除
*microTask步骤 进入microTask检查点
*更新页面渲染
*返回第一步

执行进入microtask检查点时,用户代理会执行以下步骤:
*设置microTask检查点标志为true
*当事件循环microTask执行不为空时,选择一个最先进入的microtask队列的microTask将事件循环的microtask设置为已选择的microtask运行microTask 将已经执行完成的microTask为null,移除microtask中的microtask
*清理IndexDB事务
*设置进入microtask检查点的标志为false

执行栈在执行完同步任务后,查看执行栈是否为空,如果为空就会检查微任务,队列是否为空, 如果为空就执行task否则就会一次性执行完所有的微任务

每次单个宏任务执行完毕后,检查微任务microtask队列是否为空,如果不为空的话会按照陷入先出的规则执行完全部的微任务,设置微任务队列为null,然后在执行宏任务


4.举个例子
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');
第一次执行:
Tasks:run script、 setTimeout callback

Microtasks:Promise then	

JS stack: script	
Log: script start、script end。

第二次执行:
Tasks:run script、 setTimeout callback

Microtasks:Promise2 then	

JS stack: Promise2 callback	
Log: script start、script end、promise1、promise2
执行宏任务后,检测到微任务(Microtasks)队列中不为空,执行Promise1,执行完成Promise1后,调用Promise2.then,放入微任务(Microtasks)队列中,再执行Promise2.then

第三次执行:
Tasks:setTimeout callback
Microtasks:	
JS stack: setTimeout callback
Log: script start、script end、promise1、promise2、setTimeout
当微任务(Microtasks)队列中为空时,执行宏任务(Tasks),执行setTimeout callback,打印日志
第四次执行:
Tasks:setTimeout callback
Microtasks:	
JS stack: 
Log: script start、script end、promise1、promise2、setTimeout
清空Tasks队列和JS stack。


5.再举例子
console.log('script start')
async function async1() {
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2 end') 
}
async1()
setTimeout(function() {
  console.log('setTimeout')
}, 0)
new Promise(resolve => {
  console.log('Promise')
  resolve()
})
  .then(function() {
    console.log('promise1')
  })
  .then(function() {
    console.log('promise2')
  })
console.log('script end')

这里需要先理解async/awaitasync/await 在底层转换成了 promise 和 then 回调函数。
也就是说,这是 promise 的语法糖
我们每次使用await,解释器都创建一个promise对象,然后把剩下的async函数中的操作放到then回调函数中
async await实现,离不开promise,从字面意思理解async是异步的简写,而awaitasync await的简写可以认为是等待异步方法执行完成

****关于73以下版本和73版本的区别
在老版本版本以下,先执行promise1和promise2,再执行async1。
在73版本,先执行async1再执行promise1和promise2。

在老版本中
首先,传递给 await 的值被包裹在一个 Promise 中。然后,处理程序附加到这个包装的 Promise,以便在 Promise 变为 fulfilled 后恢复该函数,并且暂停执行异步函数,一旦 promise 变为 fulfilled,恢复异步函数的执行。

每个 await 引擎必须创建两个额外的 Promise(即使右侧已经是一个 Promise)并且它需要至少三个 microtask 队列 ticks(tick为系统的相对时间单位,也被称为系统的时基,来源于定时器的周期性中断(输出脉冲),一次中断表示一个tick,也被称做一个“时钟滴答”、时标。)。

引用贺老师知乎上的一个例子
async function f() {
  await p
  consoel.log('ok')
}
简化理解为:
function f() {
  return resolve(p).then(() => {
    console.log('ok')
  })
}
如果 RESOLVE(p) 对于 p 为 promise 直接返回 p 的话,那么 p的 then 方法就会被马上调用,其回调就立即进入 job 队列。
而如果 RESOLVE(p) 严格按照标准,应该是产生一个新的 promise,尽管该 promise确定会 resolve 为 p,但这个过程本身是异步的,也就是现在进入 job 队列的是新 promise 的 resolve过程,所以该 promise 的 then 不会被立即调用,而要等到当前 job 队列执行到前述 resolve 过程才会被调用,然后其回调(也就是继续 await 之后的语句)才加入 job 队列,所以时序上就晚了。

谷歌73版本中
使用对PromiseResolve的调用来更改await的语义,以减少在公共awaitPromise情况下的转换次数。
如果传递给 await 的值已经是一个 Promise,那么这种优化避免了再次创建 Promise 包装器,在这种情况下,我们从最少三个 microtick 到只有一个 microtick。

73以下版本详细过程:
首先,打印script start,调用async1()时,返回一个Promise,所以打印出来async2 end。
每个 await,会新产生一个promise,但这个过程本身是异步的,所以该await后面不会立即调用。
继续执行同步代码,打印Promise和script end,将then函数放入微任务队列中等待执行。
同步执行完成之后,检查微任务队列是否为null,然后按照先入先出规则,依次执行。
然后先执行打印promise1,此时then的回调函数返回undefinde,此时又有then的链式调用,又放入微任务队列中,再次打印promise2。
再回到await的位置执行返回的 Promise 的 resolve 函数,这又会把 resolve 丢到微任务队列中,打印async1 end。
当微任务队列为空时,执行宏任务,打印setTimeout


6.node的eventLoop
https://juejin.cn/post/6844903764202094606#heading-30



7.setImmediate() 的setTimeout()的区别
8.Process.nextTick()

*/