关于javascript:笔记JS异步编程2Event-Loop机制

12次阅读

共计 3950 个字符,预计需要花费 10 分钟才能阅读完成。

浏览器的 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 start
promise1
async2 promise
promise2
async1 end
setTimeout
*/
  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 scheduled
someAsyncOperation 读文件后(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");
  });
});
/*
nextTick1
setImmediate
nextTick2
setTimeout
*/

正文完
 0