浏览器的Event Loop

异步实现

  1. 宏观:浏览器多线程
  2. 宏观:Event Loop事件循环,实现异步的一种机制

材料:
Event Loops规范
音讯队列和事件循环(写得很清晰)

宏工作和微工作

宏工作(macro task)

  • JavaScript 脚本执行事件
  • setTimeout/setInterval 定时器
  • setImmediate
  • I/O操作
  • UI rendering

微工作(micro task)

  • Promise
  • Object.observe
  • MutationObserver
  • postMessage

材料:宏工作和微工作(剖析的很具体)

Event Loop运行过程


紫色局部即Event Loop的过程

JS中的栈内存堆内存
栈是一个先进后出的数据结构

调用栈

  • 每调用一个函数,解释器就会把该函数增加进调用栈并开始执行。
  • 正在调用栈中执行的函数还调用了其它函数,那么新函数也将会被增加进调用栈,一旦这个函数被调用,便会立刻执行。
  • 以后函数执行结束后,解释器将其清出调用栈,继续执行以后执行环境下的残余的代码。

函数调用时会调用一些异步函数(定时器,Promise,Ajax等),相应的异步解决模块对应的线程就会往工作队列里增加事件。

从工作队列中取出一个宏工作,该宏工作执行完,调用栈为空时,会执行所有的微工作(一个须要异步执行的函数,执行机会是在主函数执行完结之后、以后宏工作完结之前)。
持续事件循环,再取工作队列中的一个宏工作执行。

Event Loop解决模型

代码剖析1:

console.log("1");setTimeout(function() {  console.log("2");}, 0);Promise.resolve().then(function() {  console.log("3");});console.log("4");// 输入:1 4 2 3

执行过程:

  1. 以后是一个宏工作,顺次执行,打印1 4后,宏工作队列空
  2. 查看微工作队列,执行Promise.resolve().then(),打印3,微工作队列空
  3. 从新渲染,工作队列里有定时器工作,执行,打印2

一个Event Loop有一个或多个工作队列,每个Event Loop有一个微工作队列。
requestAnimatinFrame不在工作队列,处于渲染阶段。(待学习)

代码示例2:
new Promise()传入的函数参数,在进行new Promise()时会同步执行

console.log("start");setTimeout(() => {  console.log("setTimeout");  new Promise(resolve => {    console.log("promise inner1");    resolve();  }).then(() => {    console.log("promise then1");  });}, 0);new Promise(resolve => {  console.log("promise inner2");  resolve();}).then(() => {  console.log("promise then2");});// start// promise inner2 这一步是同步执行的// promise then2  这里是异步,微工作// setTimeout// promise inner1// promise then1
  1. 打印start
  2. new Promise()传入的函数参数,在进行new Promise()操作时会同步执行,打印promise inner2
  3. 执行微工作,打印promise then2
  4. 事件循环,执行宏工作setTimeout,打印setTimeout
  5. 执行new Promise(),打印promise inner1
  6. 执行微工作,打印promise then1

代码示例3:

async function async1() {  console.log("async1 start");  await async2();  console.log("async1 end");}async function async2() {  return Promise.resolve().then(_ => {    console.log("async2 promise");  });}console.log("start");setTimeout(function() {  console.log("setTimeout");}, 0);async1();new Promise(function(resolve) {  console.log("promise1");  resolve();}).then(function() {  console.log("promise2");});/*start async1 startpromise1async2 promisepromise2async1 endsetTimeout*/
  1. 打印"start"
  2. 执行async函数async1,打印"async1 start"
  3. 执行await async2(); async2也是一个async函数,返回一个Promise对象,这里是一个微工作,放入微工作队列。await是期待前面函数的执行后果,因而暂停在这里。
  4. 执行new Promise(),打印"promise1",这里也有一个微工作,也放进微工作队列。
  5. 宏工作执行完,当初查看微工作队列,按程序执行,先打印"async2 promise",再打印"promise2"。当前任务完结。
  6. 进入下一个事件循环,setTimeout的工夫设置为0,必定已达到,setTimeout已在工作队列中,执行,打印"setTimeout"。

Node.js的Event Loop

Node.js架构图

  • node-core API外围JS库
  • 绑定 负责包装和裸露libuv和JS的其余低级性能
  • V8引擎 JS引擎,是JS能够运行在服务端的根底
  • libuv Node底层的I/O引擎,负责Node API的执行,将不同任务分配给不同的线程,以异步的形式将工作的执行后果返回给V8引擎,是Node异步编程的根底。

Node.js的Event Loop的六个阶段


Node.js官网文档

  1. timers(定时器) 执行定时器的回调
  2. pending callbacks(待定回调) 零碎操作的回调
  3. idle,prepare 外部应用
  4. poll(轮询) 期待新I/O事件
  5. check(检测) 执行setImmeidate回调
  6. close callbacks(敞开的回调函数) 外部应用

次要须要关注1、4、5阶段
每一个阶段都有一个callbacks的先进先出的队列须要执行。当event loop运行到一个指定阶段时,该阶段的FIFO队列将会被执行,当队列的callback执行完或者执行的callbacks数量超过该阶段的下限时,event loop会转入下一阶段。

poll阶段

次要性能:

  1. 计算应该被block多久(须要期待I/O操作)
  2. 解决poll队列的事件

代码剖析1:

const fs = require('fs');function someAsyncOperation(callback) {  fs.readFile(__dirname, callback); // 异步读文件}const timeoutScheduled = Date.now(); // 以后工夫setTimeout(() => {  const delay = Date.now() - timeoutScheduled;  console.log(`${delay}ms have passed since I was scheduled`); // 多少ms后执行}, 100);someAsyncOperation(() => {  const startCallback = Date.now();  // 延时200ms  while (Date.now() - startCallback < 200) {    // do nothing  }});/* 输入:204ms have passed since I was scheduledsomeAsyncOperation读文件后(4ms),进入poll阶段执行回调,睡眠200ms,poll空,查看是否有到工夫的定时器,执行setTimeout*/

代码剖析2:

const fs = require('fs');fs.readFile(__filename, () => {    setTimeout(() => {        console.log("setTimeout");      }, 0);      setImmediate(() => {          console.log("setImmediate");      });});/* 输入:setImmediate setTimeout读文件后,执行回调有setImmediate的回调进入check阶段没有才会期待回调退出poll队列所以先输入setImmediate*/

process.nextTick()

是一个异步的node API,但不属于event loop的阶段
调用nextTick时,会将event loop停下来,执行完nextTick的callback后再继续执行event loop

const fs = require("fs");fs.readFile(__filename, () => {  setTimeout(() => {    console.log("setTimeout");  }, 0);  setImmediate(() => {    console.log("setImmediate");    process.nextTick(() => {        console.log("nextTick2");      });  });  process.nextTick(() => {    console.log("nextTick1");  });});/*nextTick1setImmediatenextTick2setTimeout*/