Ajax-EventLoop&宏任务微任务

1.9k 词
EventLoop&宏任务微任务

EventLoop事件循环#

在开始之前,先来看两段代码:

// 练习1
console.log(1);
setTimeout(() => {
    console.log(2);
}, 2000);
console.log(3);
// 练习2
console.log(1);
setTimeout(() => {
    console.log(2);
}, 0);
console.log(3);

它们的打印结果都是:1 3 2

出现这样的结果,与 事件循环 有关

js拥有一个基于 事件循环 的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。这个模型与其他语言中的模型截然不同,比如 CJava

原因:js单线程(某一刻只能执行一行代码),为了让耗时代码不阻塞其他代码执行,设计了 事件循环模型

事件循环-执行过程#

执行顺序如下:

  • 同步任务:进入主线程后,立即执行
  • 异步任务:会先进入 Event Table;等时间到了之后,再进入 任务队列 (Event Queue)排队(排队是因为同一时间,JS 只能执行一个任务),先进先出。比如说,setTimeout(()=> {}, 1000)这种定时器任务,需要等一秒之后再进入 Event Queue
  • 当主线程的任务执行完毕之后,此时主线程处于空闲状态,于是会去读取 Event Queue 中的任务

由于主线程不断地重复获得任务、执行任务、再获取任务、再执行,所以这种机制被称为 事件循环(event loop)。

宏任务与微任务#

ES6之后引入了Promise对象,让js引擎也可以发起异步任务

异步任务分为:

  • 宏任务 :由 浏览器 环境执行的异步代码
  • 微任务 :由 js引擎 环境执行的异步代码
宏任务与微任务
类型 任务(代码) 执行所在环境
宏任务 js脚本执行事件(script) 浏览器
setTimeOut/setInterval
Ajax请求完成事件
用户交互事件
微任务 Pormise对象.then() js引擎

Promise本身是同步的,而then和catch 回调函数 是异步的

宏任务与微任务-执行顺序#

同步任务-->微任务-->宏任务

每次事件循环会取出一个宏任务进行执行。在执行宏任务的过程中,会先执行其中的同步任务,然后执行所有的微任务。当 所有微任务都执行完毕后,才会进入下一次事件循环 ,再次取出下一个宏任务进行执行。这样的循环过程会一直持续下去,直到宏任务队列中没有待执行的任务为止。

通过一段代码来分析执行顺序

console.log(1);
setTimeout(() => {
    console.log(2);
}, 0);
const p = new Promise((resolve, reject) => {
    console.log(3);
    resolve(4)
})
p.then(res => {
    console.log(res);
})
console.log(5);
  • 同步代码直接进入执行栈执行
  • 异步代码在触发条件满足后,将回调函数封装成任务,并放入相应的任务队列( Event Queue / Timer Queue )中
  • 执行栈中的任务执行完成后,首先处理 微任务队列 中的任务,直至微任务队列为空。然后再从 宏任务队列 中取出一个任务放入执行栈执行

事件循环-经典面试题#

// 目标:回答代码执行顺序
console.log(1);
setTimeout(() => {
    console.log(2);
    const p = new Promise(resolve => resolve(3))
    p.then(res => console.log(res))
}, 0);
const p = new Promise(resolve => {
    setTimeout(() => {
        console.log(4);
    }, 0);
    resolve(5)
})
p.then(res => console.log(res))
const p2 = new Promise(resolve => resolve(6))
p2.then(res => console.log(res))
console.log(7);

结果:1 7 5 6 2 3 4

分析:#

首先执行同步任务:输出1 7

  1. 第一轮事件循环:
    • 将行3-setTimeOut、9-setTimeOut放入宏任务队列
    • 14-then、16-then放入微任务队列
    • 清空微任务队列,输出5 6
    • 从宏任务队列中取出3-setTimeOut
  2. 第二轮事件循环:
    • 执行3-setTimeOut中的同步任务,输出2
    • 将6-then放入微任务队列
    • 清空微任务队列,输出3
    • 从宏任务队列中取出9-setTimeOut
  3. 第三轮事件循环:
    • 执行9-setTimeOut中的同步任务,输出4
    • 任务队列清空,结束

宏任务和微任务的执行顺序#

setTimeout(() => {
  // 宏任务
  console.log('setTimeout');
}, 0);

new Promise((resolve, reject) => {
  resolve();
  console.log('promise1'); // 同步任务
}).then((res) => {
  // 微任务
  console.log('promise then');
});

console.log('同步任务'); // 同步任务

打印结果:

promise1-> 同步任务-> promise then-> setTimeout

在宏任务中嵌套了微任务#

new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve();
    console.log('setTimeout'); // 宏任务
  }, 0);
  console.log('promise1');
}).then((res) => {
  // 微任务
  console.log('promise then');
});

console.log('同步任务');

打印结果:

promise1-> 同步任务-> setTimeout-> promise then

在执行宏任务的过程中,创建了一个微任务。但是需要先把当前这个宏任务执行完,再去创建并执行微任务

综合题#

console.log("script start")

setTimeout(() => {
  console.log("setTimeout1");
  new Promise(resolve => {
    resolve();
  }).then(() => {
    new Promise(resolve => {
      resolve();
    }).then(() => {
      console.log("then1");
    });
    console.log("then2");
  });
});

new Promise(resolve => {
  // 下面这两行代码,即便调换顺序,也不影响打印结果
  console.log("promise1");
  resolve();
}).then(() => {
  console.log("then3");
});

setTimeout(() => {
  console.log("setTimeout2");
});

console.log('同步代码');

queueMicrotask(() => {
  console.log("queueMicrotask")
});

new Promise(resolve => {
  resolve();
}).then(() => {
  console.log("then4");
});

console.log("script end");

打印结果:

// 第一次循环
script start
promise1
同步代码
script end

// 第二次循环
then3
queueMicrotask
then4

// 第三次循环
setTimeout1
then2
then1

// 第四次循环
setTimeout2

async await 题目#

console.log('script start')

async function async2() {
  console.log('async2')
}

async function async1() {
  console.log('async1 start')
  await async2();
  console.log('async1 end')
}

setTimeout(() => {
  console.log('setTimeout')
}, 0)

async1();

new Promise(resolve => {
  console.log('promise1')
  resolve();
}).then(function () {
  console.log('then1')
})

console.log('script end');

打印结果:

script start
async1 start
async2
promise1
script end

async1 end
then1

setTimeout