• 玩转事件循环


    1. 前言

    这篇文章是想跟大家一起讨论一下javascript中高大上的Event Loop事件循环机制

    代码得仔细分析,才能绕的过来,否则容易绕晕,谨慎再谨慎~~~

    事件循环主要讲的是异步执行问题,没办法,同步就是顺序执行,没什么好说的。

    至于什么线程、同步异步、调用栈、浏览器线程之类的概念,本文一概没有,有意者可自行查阅。

    2. 简介

    2.1 任务队列

    • 宏任务队列:script整体代码、setTimeout/setInterval……
    • 微任务队列:Promise

    任务队列在事件循环机制中发挥着核心作用。是我们了解事件循环机制的核心要素。

    2.2 执行顺序

    1. 无 async/await 的执行顺序

    注意点前置:

    • Promise中的函数属于同步函数

      • new Promise(function a(resolve, reject) {
          console.log('此为同步函数')
        })
        
    • setTimeout、setInterval、 Promise.then等函数,不为完成状态的情况下,不会推送到任务队列中

    图解:

    流程图执行说明:

    1. 首先执行同步代码
    2. 执行结束后查看微任务队列是否有任务,有则执行
    3. 微任务队列清空,查看宏任务队列是否有任务,有则执行
    4. 循环 2 和 3 。直至两个队列都清空
    5. 执行结束

    栗子:

    console.log('1');
    
    setTimeout(function() {
        console.log('2');
    }, 0)
    
    new Promise(function(resolve) {
        console.log('3');
        resolve();
    }).then(function() {
        console.log('4');
    });
    console.log('5')
    

    分析:

    1. 首先执行同步代码,上述代码中 console.log(1)、console.log(3)console.log(5) 都属于同步代码。优先输出。同时执行延时函数、then函数,then函数立即返回结果,推送到微任务队列

    2. 检查微任务队列,有 console.log(4) 任务,执行并输出。微任务队列结束。

    3. 延时函数倒计时完成推入宏任务队列。

    4. 检查宏任务队列,有 console.log(2) 任务,执行并输出。宏任务队列结束。

    5. 代码执行结束。

    6. 输出顺序:1,3,5,4,2

    2.async和await后的时间循环机制

    提醒: 部分文章会介绍 await 让出执行权的问题,这里先不讨论,容易绕晕。有兴趣的可以查看以下。

    await代码的执行:

    • async标记的函数属于同步函数

      • async function log () {
          console.log('同步函数')
        }
        
    • await函数中返回一个promise,则执行后为 pending 状态,将下方的代码作为微任务处理,并且,只有await函数执行之后才会执行下方代码(请看栗子4)

      • async function async2() {
          return new Promise(function(resolve) {
            console.log('3');  // 3
            resolve();
          }).then(function() {
            console.log('4'); // 6
          });
        }
        console.log(async2()) // Promise { <pending> }
        
    • await函数中执行一个promise但不return 或者 resolve 被 setTimeout/setInterval 包裹,则执行后为 结束 状态,将下方的代码作为同步代码执行

      • // await函数中执行一个promise但不return
        async function async2() {
          // 区别在于没有return
          new Promise(function(resolve) {
            console.log('3');  // 3
            resolve();
          }).then(function() {
            console.log('4'); // 6
          });
        }
        console.log(async2()) // Promise { undefined }
        
        
        // resolve 被 setTimeout/setInterval 包裹
        async function async2() {
          new Promise(function(resolve) {
            console.log('3');
            setTimeout(() => {
              resolve();
            }, 0)
          }).then(function() {
            console.log('4');
          });
        }
        console.log(async2()) // Promise { undefined }
        

    栗子1:

    async function async1() {
      console.log('1');
      await async2();
      console.log('2');
    }
    async function async2() {
      return new Promise(function(resolve) {
        console.log('3');  
        resolve();
      }).then(function() {
        console.log('4'); 
      });
    }
    console.log('5'); 
    
    setTimeout(function() {
      console.log('6'); 
    }, 0)
    async1();
    
    new Promise(function(resolve) {
      console.log('7'); 
      resolve();
    }).then(function() {
      console.log('8'); 
    });
    
    console.log('9');
    

    解析:

    1. 先执行同步,console.log(5)、console.log(1)、console.log(3)、console.log(7)、console.log(9),将console.log(4)、console.log(8)放入微任务队列。将console.log(6)放入宏任务队列。
    2. 执行console.log(4) 由于async2返回的是 promise 所以将后面的代码放入微任务队列中。此时微任务队列有两个任务 console.log(8)、 console.log(2) 。清空微任务队列,输出 8 和 2
    3. 执行宏任务队列。输出 6
    4. 输出顺序 5,1,3,7,9,4,8,2,6

    栗子2(栗子1的变种):

    // 将async2改为下面这种写法,其他代码不变
    async function async2() {
      new Promise(function(resolve) {
        console.log('3');  
        resolve();
      }).then(function() {
        console.log('4'); 
      });
    }
    

    解析:

    1. 栗子1第一步保持不变

    2. 改变第二步,执行console.log(4) 由于async2返回的不是 promise 所以直接执行后面的代码。输出 2此时微任务队列只有 console.log(8) 清空微任务队列,输出 8

    3. 栗子1第三步保持不变

    4. 输出顺序:5,1,3,7,9,4,2,8,6

    栗子3(依旧是栗子1的变种,最后一个小栗子):

    async function async1() {
      console.log('1');
      await async2();
      console.log('2');
    }
    // 将async2中的resolve函数使用setTimeout包裹 且 return一个Promise
    async function async2() {
      return new Promise(function(resolve) {
        console.log('3');
        setTimeout(() => {
          resolve();
        }, 0)
      }).then(function() {
        console.log('4');
      });
    }
    console.log('5');
    
    setTimeout(function() {
      console.log('6');
    }, 0)
    async1();
    
    new Promise(function(resolve) {
      console.log('7');
      resolve();
    }).then(function() {
      console.log('8');
    });
    

    解析:

    1. 先执行同步,console.log(5)、console.log(1)、console.log(3)、console.log(7)、console.log(9),将console.log(8)放入微任务队列。将console.log(6)和 console.log(4)放入宏任务队列。
    2. 执行微任务console.log(8),输出 8
    3. 执行宏任务console.log(6),输出6
    4. 由于async2返回一个Promise,所以只能等到async2执行之后才会将console.log(2)推入到微任务队列。
    5. 执行宏任务console.log(4),输出4并将 console.log(2)推入到微任务中。
    6. 执行微任务console.log(2),输出2
    7. 输出顺序:5,1,3,7,9,8,6,4,2

    栗子4(真的是最后一个小栗子了):

    // 将栗子3的async2函数改为如下写法
    async function async2() {
      new Promise(function(resolve) {
        console.log('3');
        setTimeout(() => {
          resolve();
        }, 0)
      }).then(function() {
        console.log('4');
      });
    }
    

    解析:

    1. 栗子3第一步不变
    2. 由于async2不返回Promise,所以会将console.log(2)作为同步代码执行,第二步执行同步任务console.log(2) 输出2
    3. 执行微任务console.log(8),输出 8
    4. 执行宏任务console.log(6),输出6
    5. 执行宏任务console.log(4),输出4
    6. 输出顺序:5,1,3,7,9,2,8,6,4

    无论你是否认真的查看了上面的文章,首先恭喜你可以看到这里,事件队列一直是比较难以理解的javascript知识点。还是衷心的希望这篇文章能带给你不一样的理解。也祝福正在看文章的你技术越来越好。

  • 相关阅读:
    Python中的Dictionary
    Python中的list
    Python的string模块化方法
    Python字符串格式化表达式和格式化方法
    Python中的slice操作
    Python中的字符串
    华为笔试——C++进制转换
    华为笔试——C++消重输出
    华为笔试——C++转换字符串问题
    C++数字三角形问题与dp算法
  • 原文地址:https://www.cnblogs.com/yancyCathy/p/13406907.html
Copyright © 2020-2023  润新知