产生宏任务和微任务的方式

产生宏任务的方式

  1. script中的代码块
  2. setTimeout()
  3. setInterval()
  4. setImmediate()(非标准的方法,IE和nodejs中支持)
  5. postMessage
  6. I/O
  7. UI交互事件

    产生微任务的方式

  8. new Promise().then(回调)(promise的构造函数是同步执行,promise.then中的函数是异步执行)

  9. MutationObserver 这个对象,可以去监听dom对象的改变,当dom改变之后,可以执行一个回调函数,这个回调函数在微任务队列中
  10. queueMicrotask() 单纯的开启微任务

    关于promise的解释

  11. promise的构造函数是同步执行,promise.then中的函数是异步执行

  12. 构造函数中的 resolve 或 reject 只有第一次执行有效,多次调用没有任何作用。promise 状态一旦改变则不能再变。 promise 有 3 种状 态: pending、fulfilled 或 rejected。 状态改变只能是 pending->fulfilled 或者 pending-> rejected,状态一旦改变则不能再变。

  13. promise 的 .then 或者 .catch 可以被调用多次,但这里 Promise 构造函数只执行一次。或者说 promise 内部状态一经改变,并且有了一个值,那么后续每次调用 .then 或者 .catch 都会直接拿到该值。

  14. 如果在一个then()中没有返回一个新的promise,则return 什么下一个then就接受什么,如果then中没有return,则默认return的是 undefined.
  15. then()中包含.then()的嵌套情况 then()的嵌套会先将内部的then()执行完毕再继续执行外部的then();在多个then嵌套时建议将其展开,将then()放在同一级,这样代码更清晰。
  16. catch和then的连用 如果每一步都有可能出现错误,那么就可能出现catch后面接上then的情况
  17. Promise.all() 将多个Promise批量执行,所有的Promise都完毕之后返回一个新的Promise
  18. Promise.race() 和Promise.all()差不多,区别就是传入的数组中有一个Promise完成了则整个Promise完成了。

关于Promise的解释参考地址 promise执行顺序总结

  1. const promise = new Promise((resolve, reject) => {
  2. resolve('success1')
  3. reject('error')
  4. resolve('success2')
  5. })
  6. promise
  7. .then((res) => {
  8. console.log('then: ', res)
  9. })
  10. .catch((err) => {
  11. console.log('catch: ', err)
  12. })
  13. // 执行的then是 success1

第五条的代码

  1. console.log('start');
  2. new Promise((resolve,reject)=>{
  3. setTimeout(function(){
  4. console.log('step');
  5. resolve(110);
  6. },1000)
  7. })
  8. .then((value)=>{
  9. return new Promise((resolve,reject)=>{
  10. setTimeout(function(){
  11. console.log('step1');
  12. resolve(value);
  13. },1000)
  14. })
  15. .then((value)=>{
  16. console.log('step 1-1');
  17. return value;
  18. })
  19. .then((value)=>{
  20. console.log('step 1-2');
  21. return value;
  22. })
  23. })
  24. .then((value)=>{
  25. console.log(value);
  26. console.log('step 2');
  27. })
  28. /* start step step1 step 1-1 step 1-2 110 step 2 */
  29. //展开之后的代码
  30. console.log('start');
  31. new Promise((resolve,reject)=>{
  32. setTimeout(function(){
  33. console.log('step');
  34. resolve(110);
  35. },1000)
  36. })
  37. .then((value)=>{
  38. return new Promise((resolve,reject)=>{
  39. setTimeout(function(){
  40. console.log('step1');
  41. resolve(value);
  42. },1000)
  43. })
  44. })
  45. .then((value)=>{
  46. console.log('step 1-1');
  47. return value;
  48. })
  49. .then((value)=>{
  50. console.log('step 1-2');
  51. return value;
  52. })
  53. .then((value)=>{
  54. console.log(value);
  55. console.log('step 2');
  56. })

浏览器中的Event loop

  • 只有两个任务队列,宏队列和微队列
  • 每次宏任务执行完之前会先把微任务执行完毕
  • 同一次事件循环中,微任务永远在宏任务之前执行。
  • 微任务执行是在调用栈上的代码执行完,渲染之前执行

微任务执行7.png

  1. setTimeout(() => {
  2. console.log('s1');
  3. Promise.resolve().then(() => {
  4. console.log('p1');
  5. })
  6. Promise.resolve().then(() => {
  7. console.log('p2');
  8. })
  9. })
  10. setTimeout(() => {
  11. console.log('s2');
  12. Promise.resolve().then(() => {
  13. console.log('p3');
  14. })
  15. Promise.resolve().then(() => {
  16. console.log('p4');
  17. })
  18. })
  19. // 执行顺序s1 p1 p2 s2 p3 p4

浏览器中完整事件环执行顺序

下面的东西看着有点懵的话请务必看一下大佬的文章(https://segmentfault.com/a/1190000022805523#item-2

  • 从上至下执行所有的同步代码
  • 执行过程中将遇到的宏任务与微任务添加至相应的任务队列
  • 同步代码执行完毕后,执行满足条件的微任务回调
  • 微任务队列执行完毕后执行所有满足需求的宏任务回调(先进先出)
  • 循环事件环操作
  • 注意:每执行一个宏任务之后就会立刻检查微任务队列
  • 注意:promise传入的函数executor会在传入的同时被执行,如果里面有同步代码会被优先执行
  • 连续调用then,第一个then执行完之后,会将下一个then放入队列中
  • 解释:promise的构造函数是同步执行,promise.then中的函数是异步执行。
  • 同步 -> 微任务 ->宏任务-微任务-宏任务-微任务
  • 在Call Stack中不仅会形成宏任务队列,还会形成微任务队列,在每一次Call Stack清空后都需要将微任务队列清空(全部执行)

    宏任务、微任务的执行顺序 执行顺序:先执行同步代码,遇到异步宏任务则将异步宏任务放入宏任务队列中,遇到异步微任务则将异步微任务放入微任务队列中,当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,微任务执行完毕后再将异步宏任务从队列中调入主线程执行,一直循环直至所有任务执行完毕。

  1. Call Stack清空
  2. 执行当前的微任务
  3. 尝试DOM渲染
  4. 触发Event Loop
    微任务和宏任务的区别
  • 宏任务:DOM渲染后触发,由浏览器规定(Web APIs)
  • 微任务:DOM渲染前执行,微任务是ES6语法规定

    event loop和Dom渲染
  • 每次Call Stack清空(即每次轮询结束),即同步任务执行完

  • 都是Dom重写渲染的机会,Dom结构如有改变则重新渲染
  • 然后再去触发下一次Event Loop

微任务和宏任务之间存在dom渲染

  1. const main = document.getElementById('main');
  2. const frg = document.createDocumentFragment();
  3. for(let i = 0; i < 10; i++) {
  4. const li = document.createElement('li');
  5. li.innerHTML = i;
  6. frg.appendChild(li);
  7. }
  8. main.appendChild(frg);
  9. new Promise((resolve) => {
  10. resolve();
  11. }).then(() => {
  12. console.log('微任务已经执行');
  13. alert('dom 还未插入')
  14. });
  15. setTimeout(() => {
  16. console.log('宏任务执行');
  17. alert('dom 已经插入')
  18. });
  1. setTimeout(() => {
  2. console.log('s1');
  3. Promise.resolve().then(() => {
  4. console.log('p2');
  5. })
  6. Promise.resolve().then(() => {
  7. console.log('p3');
  8. })
  9. })
  10. Promise.resolve().then(() => {
  11. console.log('p1');
  12. setTimeout(() => {
  13. console.log('s2');
  14. });
  15. setTimeout(() => {
  16. console.log('s3');
  17. });
  18. })
  19. // 按照顺序压入宏任务和微任务队列中
  20. // p1 s1 p2 p3 s2 s3

代码示例

executor会在传入的同时被执行,v8的实现中c的micro task被创建得更早

JavaScript宏任务和微任务、事件循环及经典案例

关于连续调用then

then1执行之后会将then2放入微任务队列,之前的微任务队列在执行完then1已经空了,但却新放入了then2,所以会执行then2,then2之后then3 。。。一直循环清空微任务队列,才会去执行宏任务

  1. new Promise((res, rej) => {
  2. res()
  3. }).then((v) => { // then1
  4. console.log(1)
  5. return 2
  6. }).then(v => { // then2
  7. console.log(v)
  8. }).then(v => { // then3
  9. console.log(v)
  10. })
  11. setTimeout(() => {
  12. console.log('setr')
  13. }, 0);

关于async await

  • async最终返回的是一个promise, await 后面的代码(指所有)相当于promise.then的代码,会添加到微任务队列中
  • 如果没有改变promise的状态,那么接下来的代码也不会执行
  • await后面如果跟一个promise对象,await将等待这个promise对象的resolve状态的值value,且将这个值返回给前面的变量,此时的promise对象的状态是一个pending状态,没有resolve状态值,所以什么也打印不了 ```javascript async function t2(){ let a = await new Promise((resolve) => {}) console.log(a) } t2() // 不会执行

async function t1(){ let a = await “lagou” console.log(a) } t1() // lagou

<a name="kZ0WX"></a>
##### await的执行过程<br />![await.png](https://cdn.nlark.com/yuque/0/2022/png/22628793/1653797414292-270df2b0-3827-41f9-820b-9f5d1dabdbea.png#clientId=u80143c46-7f41-4&crop=0&crop=0&crop=1&crop=1&from=ui&id=u9fddd3c1&margin=%5Bobject%20Object%5D&name=await.png&originHeight=856&originWidth=841&originalType=binary&ratio=1&rotation=0&showTitle=false&size=233563&status=done&style=none&taskId=ua8d541e3-36a9-48f9-ac64-7a7fdcc5896&title=)
<a name="DXPnw"></a>
#### 经典面试题
```javascript
async function asycn1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}

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

console.log('script start');

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

asycn1();

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

console.log('script end');


先执行宏任务(当前代码块也算是宏任务),然后执行当前宏任务产生的微任务,然后接着执行宏任务

从上往下执行代码,先执行同步代码,输出 script start
遇到setTimeout,现把 setTimeout 的代码放到宏任务队列中
执行 async1(),输出 async1 start, 然后执行 async2(), 输出 async2,把 async2() 后面的代码 console.log('async1 end')放到微任务队列中
接着往下执行,输出 promise1,把 .then()放到微任务队列中;注意Promise本身是同步的立即执行函数,.then是异步执行函数
接着往下执行, 输出 script end。同步代码(同时也是宏任务)执行完成,接下来开始执行刚才放到微任务中的代码
依次执行微任务中的代码,依次输出 async1 end、 promise2, 微任务中的代码执行完成后,开始执行宏任务中的代码,输出 setTimeout

最后的执行结果如下

script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout



问:请问为什么第一题的console.log('async1 end')会被放到微任务里?

因为他前面有一个await,并且这个await等待的是一个promise,所以会阻塞后面的代码执行,await相当于promise的then

大佬的面试经过

https://zhuanlan.zhihu.com/p/521550940

  1. 遍历 microtask 队列时新增了 microtask,本轮循环执行新增的 microtask,直至 microtask 队列长度为 0
  2. 注意审题。microtask 队列、promise 和 setTimeout 的源码我全都看过,还写过文章,最后还是答错了,要多做面试题,提高手感
  3. 数组的相关方法,循环次数预先确定,如果在循环过程中数组长度发生改变,循环次数不变。microtask 队列的循环则相反,循环过程中新增了 microtask,循环次数增加

    NodeJs事件循环机制

    Nodejs事件循环机制2.png
  • nodejs中一共有6个任务队列

    队列说明

  1. timer:执行setTimeout与setInterval回调
  2. pending callback:执行系统操作的回调,例如:tcp, udp
  3. idle、prepare:只在系统内部进行使用
  4. poll:执行与 I/O 相关的回调(文件读写)
  5. check:执行setImmediate中的回调
  6. close callback :执行close事件的回调

微任务:process.nextTick的执行优先级高于Promise

Nodejs中完整的事件环执行顺序

  • 执行同步代码,将不同的任务添加至相应的队列
  • 所有同步代码执行后会去执行满足条件微任务(proces.nextTick执行优先级高于Promise)
  • 所有微任务代码执行后会执行timer队列中满足的宏任务(setTimeout与setInterval等)
  • timer中的所有宏任务执行完成后就会依次切换队列
  • 注意:微任务执行1. 同步执行完之后会执行满足条件的微任务,2.在完成队列切换之前会先清空微任务代码 ```javascript // 宏任务 timer setTimeout(() => { console.log(‘s1’); })

// 微任务 Promise.resolve().then(() => { console.log(‘p1’); })

// 同步任务 console.log(‘start’);

// 微任务 process.nextTick(() => { console.log(‘tick’); })

// 宏任务 check setImmediate(() => { console.log(‘setImmediate’); })

// 同步任务 console.log(‘end’);

// 执行顺序:start end tick p1 s1 setImmediate

// 事件队列执行顺序 微任务 —> timer — > poll —-> check

// 微任务中process.nextTick执行优先级高于Promise

<a name="RtINu"></a>
#### Nodejs事件环理解
```javascript
// timer
setTimeout(() => {
  console.log("s1");
  // 微任务
  Promise.resolve().then(() => {
    console.log("p1");
  });
  // 微任务
  process.nextTick(() => {
    console.log("t1");
  });
});

// 微任务
Promise.resolve().then(() => {
  console.log("p2");
});

console.log("start");

// timer
setTimeout(() => {
  console.log("s2");

  // 微任务
  Promise.resolve().then(() => {
    console.log("p3");
  });

  // 微任务
  process.nextTick(() => {
    console.log("t2");
  });
});

console.log("end");

// strat  end  p2  s1  s2  t1  t2  p1  p3

NodeJs与浏览器事件环区别

  1. 任务队列数不同
  2. Nodejs微任务执行时机不同
  3. 微任务优先级不同

    任务队列数

  • 浏览器中只有两个任务队列(宏任务和微任务)
  • Nodejs中有6个事件队列(timer,pending callback,idle prepare,poll,check, close callback)

    微任务执行时机

  • 二者都会在同步代码执行完后执行微任务

  • 浏览器平台下每当一个宏任务执行完毕后就清空微任务
  • Nodejs平台在事件队列切换时会去清空微任务

    微任务执行优先级

  • 浏览器事件环中,微任务存放事件队列执行顺序,先进先出

  • Nodejs中 process.nextTick执行优先于promise.then

    Nodejs事件环常见问题

  • 执行顺序不固定 ```javascript // timer如果有延迟,会继续向下走,执行check里面的代码,这就造成了输出不一致 setTimeout(() => { console.log(‘timerout’); },0);

// check // 不需要设置时间,没有延迟 setImmediate(() => { console.log(‘immediate’); })

// node中, timer poll check


- readFile在poll里面
- 放在了IO回调当中,setImmediate的回调和setTimeout中的回调顺序就固定了
```javascript
const fs = require("fs");

// poll
fs.readFile("/m1.js", () => {

  // timer
  setTimeout(() => {
    console.log("timeout");  // 后执行
  }, 0);

  // check
  setImmediate(() => {
    console.log("setImmediate"); // 先执行
  });
});

// readFile存放在poll里面,接着向下执行,执行check里面的回调,然后才会去timer里面

推荐文章

浅析setTimeout与Promise
JavaScript宏任务和微任务、事件循环及经典案例
关于promise的解释
强烈推荐 JavaScript中的Event Loop(事件循环)机制
microtask 队列与 async/await 源码分析
Promise V8 源码分析(一)