Maison  >  Article  >  interface Web  >  Une combinaison d'images et de texte vous aidera à comprendre la boucle d'événements dans Nodejs

Une combinaison d'images et de texte vous aidera à comprendre la boucle d'événements dans Nodejs

青灯夜游
青灯夜游avant
2021-09-30 10:22:382276parcourir

Cet article utilise une combinaison d'images et de texte pour vous aider à comprendre la boucle d'événements dans Nodejs J'espère qu'il sera utile à tout le monde !

Une combinaison d'images et de texte vous aidera à comprendre la boucle d'événements dans Nodejs

Le texte intégral suivant compte 7 000 mots, veuillez le lire lorsque vous avez les pensées claires et êtes plein d'énergie. Je vous garantis que vous ne l’oublierez pas longtemps après l’avoir compris. [Apprentissage recommandé : "Tutoriel Nodejs"]

Une combinaison dimages et de texte vous aidera à comprendre la boucle dévénements dans Nodejs

Boucle d'événement Node

Le langage utilisé en bas de Node libuv est un langage C++. Il est utilisé pour faire fonctionner le système d’exploitation sous-jacent et encapsule l’interface du système d’exploitation. La boucle d'événements du nœud est également écrite en utilisant libuv, donc le cycle de vie du nœud est toujours différent de celui du navigateur. libuv来写的,所以Node生命周期和浏览器的还是有区别的。

因为Node和操作系统打交道,所以事件循环比较复杂,也有一些自己特有的API。
事件循环在不同的操作系统里有一些细微的差异。这将涉及到操作系统的知识,暂时不表。 本次只介绍JS主线程中,Node的运作流程。Node的其他线程暂时也不扩展。

事件循环图

说好的一张图,也不卖关子。下边这张图搞清楚了,事件循环就学会了。

Une combinaison dimages et de texte vous aidera à comprendre la boucle dévénements dans Nodejs

事件循环图

Une combinaison dimages et de texte vous aidera à comprendre la boucle dévénements dans Nodejs

事件循环图-结构

为了让大家先有个大局观,先贴一张目录结构图在前边:

Une combinaison dimages et de texte vous aidera à comprendre la boucle dévénements dans Nodejs

目录

接下来详细展开说说

主线程

Une combinaison dimages et de texte vous aidera à comprendre la boucle dévénements dans Nodejs

主线程

上图中,几个色块的含义:

  • main:启动入口文件,运行主函数
  • event loop:检查是否要进入事件循环
    • 检查其他线程里是否还有待处理事项
    • 检查其他任务是否还在进行中(比如计时器、文件读取操作等任务是否完成)
    • 有以上情况,进入事件循环,运行其他任务
      事件循环的过程:沿着从timers到close callbacks这个流程,走一圈。到event loop看是否结束,没结束再走一圈。
  • over
  • Étant donné que Node s'occupe du système d'exploitation, la boucle d'événements est relativement complexe et possède ses propres API uniques.
    La boucle d'événements présente quelques différences subtiles selon les systèmes d'exploitation. Cela nécessitera une connaissance du système d'exploitation, qui n'est pas répertorié pour l'instant. Cette fois, nous introduisons uniquement le processus de fonctionnement de Node dans le thread principal JS. Les autres threads de Node ne seront pas étendus pour le moment.

Diagramme de boucle d'événement

L'image promise n'est pas grave. Une fois que vous aurez compris l’image ci-dessous, vous apprendrez la boucle des événements. Une combinaison dimages et de texte vous aidera à comprendre la boucle dévénements dans Nodejs

Une combinaison dimages et de texte vous aidera à comprendre la boucle dévénements dans Nodejs

Événement Diagramme de cycle

Une combinaison dimages et de texte vous aidera à comprendre la boucle dévénements dans Nodejs

Diagramme de boucle d'événement - Structure

Afin de donner à chacun une

vue d'ensemble, publiez d'abord un diagramme de structure de répertoires devant : Une combinaison dimages et de texte vous aidera à comprendre la boucle dévénements dans NodejsTable des matières

Parlons-en en détail

  • Thème principal

  • 5. png
  • Thème principal

  • Dans l'image ci-dessus, la signification de plusieurs blocs de couleur :
    • main : Démarrez le fichier d'entrée et exécutez la fonction principale

  • boucle d'événements code> : Vérifiez s'il faut entrer dans la boucle d'événements
    • Vérifiez s'il y a encore des éléments en attente dans d'autres threads

      Vérifiez si d'autres tâches sont toujours en cours (telles que des minuteries, des opérations de lecture de fichiers, etc. sont terminés)

      Il y a la situation ci-dessus, entrez dans la boucle d'événements et exécutez d'autres tâches
      Le processus de la boucle d'événements : suivez le processus depuis les minuteries jusqu'à la fermeture des rappels et marchez en cercle. Accédez à la boucle d'événements pour voir si elle est terminée. Sinon, faites le tour.
    • over : Tout est terminé, terminé

    • Cercle de boucle d'événement

    Une combinaison dimages et de texte vous aidera à comprendre la boucle dévénements dans Nodejs

    Cercle de boucle d'événement

    🎜Le cercle gris sur l'image est lié au système d'exploitation , n’est pas l’objet de l’analyse de ce chapitre. Concentrez-vous sur les cercles jaunes et orange et sur la case orange au milieu. 🎜🎜🎜Nous appelons chaque cycle de boucle d'événement "une boucle", également appelé "un sondage", également appelé "un Tick". 🎜🎜🎜🎜🎜🎜Un cycle passe par six étapes : 🎜🎜🎜🎜🎜🎜🎜🎜timers (les fonctions de rappel telles que setTimeout, setInterval, etc. sont stockées à l'intérieur) 🎜🎜🎜🎜🎜en attente de rappel🎜🎜 🎜 🎜 inactif préparer🎜🎜🎜🎜🎜poll : file d'attente d'interrogation (les rappels autres que les minuteries et les vérifications sont stockés ici) 🎜🎜🎜🎜🎜🎜check : phase de vérification (les rappels utilisant setImmediate entreront directement dans cette file d'attente) 🎜🎜🎜🎜🎜 fermer les rappels🎜 🎜🎜🎜🎜🎜🎜Cette fois-ci, nous nous concentrons uniquement sur les trois points clés marqués en rouge ci-dessus. 🎜

    Comment ça marche

    • Chaque étape maintient une file d'attente d'événements. Vous pouvez considérer chaque cercle comme une file d’attente d’événements.
    • Ceci est différent du navigateur. Le navigateur a jusqu'à deux files d'attente (file d'attente macro et file d'attente micro). Mais il y a six files d'attente dans le nœud. Après être arrivé à une file d'attente, vérifiez s'il y a une tâche dans la file d'attente (c'est-à-dire s'il y a une fonction de rappel) qui doit être exécutée. S'il y en a, exécutez-les dans l'ordre jusqu'à ce que toutes les exécutions soient terminées et que la file d'attente soit vidée.
    • S'il n'y a pas de tâche, passez à la file d'attente suivante pour vérifier. Jusqu'à ce que toutes les files d'attente soient vérifiées, cela est considéré comme un sondage.
    • Parmi eux,
    • file d'attente.
    • timerspending callbackidle prepare等执行完毕后,到达poll

    Comment fonctionne la file d'attente des minuteriesles minuteries ne sont pas une vraie file d'attente, elles stockent les minuteries en interne.

    Chaque fois qu'il atteint cette file d'attente, tous les minuteurs du fil de minuterie seront vérifiés. Plusieurs minuteurs du fil de minuterie sont triés par ordre chronologique.


    Processus de vérification : calculez chaque minuterie dans l'ordre et calculez si le temps écoulé entre le moment où la minuterie a commencé à compter et l'heure actuelle correspond au réglage du paramètre d'intervalle de minuterie (par exemple, 1 000 ms, calculez s'il y a 1 minute à partir du moment où la minuterie a démarré. je compte jusqu'à maintenant) ). Lorsqu'une vérification de minuterie réussit, sa fonction de rappel est exécutée.

    Comment fonctionne la file d'attente d'interrogation

    S'il y a des fonctions de rappel dans l'interrogation qui doivent être exécutées, les rappels seront exécutés dans l'ordre jusqu'à ce que la file d'attente soit effacée.
    • S'il n'y a aucune fonction de rappel dans le sondage qui doit être exécutée, la file d'attente est déjà vide. Il attendra ici que les rappels apparaissent dans d'autres files d'attente.
    • Si des rappels apparaissent dans d'autres files d'attente, il passera du sondage vers le bas, terminera cette étape et entrera dans l'étape suivante.
      • S'il n'y a pas de rappel dans les autres files d'attente, continuez à attendre dans la file d'attente d'interrogation jusqu'à ce qu'un rappel se produise dans une file d'attente avant de commencer le travail. (C'est ainsi qu'une personne paresseuse fait les choses)

    Prenez un exemple pour trier le flux d'événements
    setTimeout(() => {
      console.log('object');
    }, 5000)
    console.log('node');

    Triez le flux d'événements du code ci-dessus

    Entrez dans le thread principal et exécutez setTimeout( ). La fonction de rappel est placée dans la file d'attente des minuteries de file d'attente asynchrones en tant que tâche asynchrone et ne sera pas exécutée temporairement.
    • Continuez vers le bas, exécutez la console derrière le minuteur et imprimez "node".
    • Déterminez s'il existe une boucle d'événements. Oui, passez par un cycle d'interrogation : des minuteries - rappel en attente - préparation inactive...
    • à
    • poll
    • file d'attente pour arrêter la boucle et attendre. Comme cela ne fait pas 5 secondes à ce moment-là, la file d'attente des minuteries n'a aucune tâche, elle est donc restée bloquée dans la file d'attente des sondages, et en même temps, elle interroge pour vérifier s'il y a des tâches dans d'autres files d'attente.
      Attendez 5 secondes pour arriver. Le rappel setTimeout est inséré dans les minuteries. L'interrogation de routine vérifie qu'il y a des tâches dans la file d'attente des minuteries, puis elle descend et atteint les minuteries après avoir vérifié et fermé les rappels. Effacez la file d’attente des minuteries.
    • Continuez à interroger et attendez, en demandant si la boucle d'événement est toujours nécessaire. Sinon, elle atteindra la fin.

    Pour comprendre ce problème, regardez l'analyse du code et du processus ci-dessous :
    setTimeout(function t1() {
      console.log('setTimeout');
    }, 5000)
    console.log('node 生命周期');
    
    const http = require('http')
    
    const server = http.createServer(function h1() {
      console.log('请求回调');
    });
    
    server.listen(8080)
    L'analyse du code est la suivante :

    Comme d'habitude, exécutez d'abord le thread principal, imprimez la "vie du nœud cycle", introduisez http puis créez le service http.
    • Ensuite, la boucle d'événements vérifie s'il existe des tâches asynchrones et détecte qu'il existe des tâches de minuterie et des tâches de demande. Entrez donc dans la boucle des événements.
    • S'il n'y a aucune tâche dans les six files d'attente, attendez dans la file d'attente des sondages. Comme le montre l'image ci-dessous :
    • Une combinaison dimages et de texte vous aidera à comprendre la boucle dévénements dans Nodejs

      Après cinq secondes, il y a une tâche dans les minuteries, et le processus démarre de l'interrogation vers le bas, et après avoir parcouru les files d'attente de vérification et de fermeture des rappels, il atteint la boucle d'événements. .
    • La boucle d'événements vérifie s'il existe des tâches asynchrones et détecte qu'il existe des tâches de minuterie et des tâches de demande. Entrez donc à nouveau dans la boucle des événements.
    • Lorsque vous arrivez à la file d'attente des minuteries et constatez qu'il y a une tâche de fonction de rappel, vous exécuterez les rappels dans l'ordre, effacerez la file d'attente des minuteries (bien sûr, il n'y a qu'un seul rappel qui arrive après 5 secondes, alors terminez simplement la exécution directement) et imprimez "setTimeout". Comme le montre l'image ci-dessous
    • Une combinaison dimages et de texte vous aidera à comprendre la boucle dévénements dans Nodejs

      Après avoir effacé la file d'attente des minuteries, le sondage continue jusqu'à la file d'attente des sondages. Puisque la file d'attente des sondages est maintenant une file d'attente vide, elle attend ici.
    • Plus tard, en supposant qu'une demande utilisateur soit envoyée, la fonction de rappel h1 est placée dans la file d'attente des sondages. Il y a donc des fonctions de rappel dans poll qui doivent être exécutées, et les rappels sont exécutés en séquence jusqu'à ce que la file d'attente d'interrogation soit effacée.
    • La file d'attente des sondages est vidée. À ce moment, la file d'attente des sondages est vide, continuez d'attendre.
    • Une combinaison dimages et de texte vous aidera à comprendre la boucle dévénements dans Nodejs

    • 由于node线程一直holding在poll队列,等很长一段时间还是没有任务来临时,会自动断开等待(不自信表现),向下执行轮询流程,经过check、close callbacks后到达event loop
    • 到了event loop后,检查是否有异步任务,检查发现有请求任务。(此时定时器任务已经执行完毕,所以没有了),则继续再次进入事件循环。
    • 到达poll队列,再次holding……
    • 再等很长时间没有任务来临,自动断开到even loop(再补充一点无任务的循环情况)
    • 再次回到poll队列挂起
    • 无限循环……

    梳理事件循环流程图:

    注意:下图中的“是否有任务”的说法表示“是否有本队列的任务”。

    1Une combinaison dimages et de texte vous aidera à comprendre la boucle dévénements dans Nodejs

    event loop流程梳理

    再用一个典型的例子验证下流程:

    const startTime = new Date();
    
    setTimeout(function f1() {
      console.log('setTimeout', new Date(), new Date() - startTime);
    }, 200)
    
    console.log('node 生命周期', startTime);
    
    const fs = require('fs')
    
    fs.readFile('./poll.js', 'utf-8', function fsFunc(err, data) {
      const fsTime = new Date()
      console.log('fs', fsTime);
      while (new Date() - fsTime < 300) {
      }
      console.log(&#39;结束死循环&#39;, new Date());
    });

    连续运行三遍,打印结果如下:

    1Une combinaison dimages et de texte vous aidera à comprendre la boucle dévénements dans Nodejs

    执行流程解析:

    • 执行全局上下文,打印「node 生命周期 + 时间」

    • 询问是否有event loop

    • 有,进入timers队列,检查没有计时器(cpu处理速度可以,这时还没到200ms)

    • 轮询进入到poll,读文件还没读完(比如此时才用了20ms),因此poll队列是空的,也没有任务回调

    • 在poll队列等待……不断轮询看有没有回调

    • 文件读完,poll队列有了fsFunc回调函数,并且被执行,输出「fs + 时间」

    • 在while死循环那里卡300毫秒,

    • 死循环卡到200ms的时候,f1回调进入timers队列。但此时poll队列很忙,占用了线程,不会向下执行。

    • 直到300ms后poll队列清空,输出「结束死循环 + 时间」

    • event loop赶紧向下走

    • 再来一轮到timers,执行timers队列里的f1回调。于是看到「setTimeout + 时间」

    • timers队列清空,回到poll队列,没有任务,等待一会。

    • 等待时间够长后,向下回到event loop。

    • event loop检查没有其他异步任务了,结束线程,整个程序over退出。

    check 阶段

    检查阶段(使用 setImmediate 的回调会直接进入这个队列)

    check队列的实际工作原理

    真正的队列,里边扔的就是待执行的回调函数的集合。类似[fn,fn]这种形式的。
    每次到达check这个队列后,立即按顺序执行回调函数即可【类似于[fn1,fn2].forEach((fn)=>fn())的感觉】

    所以说,setImmediate不是一个计时器的概念。

    如果你去面试,涉及到Node环节,可能会遇到下边这个问题:setImmediate和setTimeout(0)谁更快。

    setImmediate() 与 setTimeout(0) 的对比

    • setImmediate的回调是异步的,和setTimeout回调性质一致。
    • setImmediate回调在check队列,setTimeout回调在timers队列(概念意义,实际在计时器线程,只是setTimeout在timers队列做检查调用而已。详细看timers的工作原理)。
    • setImmediate函数调用后,回调函数会立即push到check队列,并在下次eventloop时被执行。setTimeout函数调用后,计时器线程增加一个定时器任务,下次eventloop时会在timers阶段里检查判断定时器任务是否到达时间,到了则执行回调函数。
    • 综上,setImmediate的运算速度比setTimeout(0)的要快,因为setTimeout还需要开计时器线程,并增加计算的开销。

    二者的效果差不多。但是执行顺序不定

    观察以下代码:

    setTimeout(() => {
      console.log(&#39;setTimeout&#39;);
    }, 0);
    
    setImmediate(() => {
      console.log(&#39;setImmediate&#39;);
    });

    多次反复运行,执行效果如下:

    1Une combinaison dimages et de texte vous aidera à comprendre la boucle dévénements dans Nodejs

    顺序不定

    可以看到多次运行,两句console.log打印的顺序不定。
    这是因为setTimeout的间隔数最小填1,虽然下边代码填了0。但实际计算机执行当1ms算。(这里注意和浏览器的计时器区分。在浏览器中,setInterval的最小间隔数为10ms,小于10ms则会被设置为10;设备供电状态下,间隔最小为16.6ms。)

    以上代码,主线程运行的时候,setTimeout函数调用,计时器线程增加一个定时器任务。setImmediate函数调用后,其回调函数立即push到check队列。主线程执行完毕。

    eventloop判断时,发现timers和check队列有内容,进入异步轮询:

    第一种情况:等到了timers里这段时间,可能还没有1ms的时间,定时器任务间隔时间的条件不成立所以timers里还没有回调函数。继续向下到了check队列里,这时候setImmediate的回调函数早已等候多时,直接执行。而再下次eventloop到达timers队列,定时器也早已成熟,才会执行setTimeout的回调任务。于是顺序就是「setImmediate -> setTimeout」。

    第二种情况:但也有可能到了timers阶段时,超过了1ms。于是计算定时器条件成立,setTimeout的回调函数被直接执行。eventloop再向下到达check队列执行setImmediate的回调。最终顺序就是「setTimeout -> setImmediate」了。

    所以,只比较这两个函数的情况下,二者的执行顺序最终结果取决于当下计算机的运行环境以及运行速度。

    二者时间差距的对比代码

    ------------------setTimeout测试:-------------------
    let i = 0;
    console.time(&#39;setTimeout&#39;);
    function test() {
      if (i < 1000) {
        setTimeout(test, 0)
        i++
      } else {
        console.timeEnd(&#39;setTimeout&#39;);
      }
    }
    test();
    
    ------------------setImmediate测试:-------------------
    let i = 0;
    console.time(&#39;setImmediate&#39;);
    function test() {
      if (i < 1000) {
        setImmediate(test)
        i++
      } else {
        console.timeEnd(&#39;setImmediate&#39;);
      }
    }
    test();

    运行观察时间差距:

    1Une combinaison dimages et de texte vous aidera à comprendre la boucle dévénements dans Nodejs

    setTimeout与setImmediate时间差距

    可见setTimeout远比setImmediate耗时多得多
    这是因为setTimeout不仅有主代码执行的时间消耗。还有在timers队列里,对于计时器线程中各个定时任务的计算时间。

    结合poll队列的面试题(考察timers、poll和check的执行顺序)

    如果你看懂了上边的事件循环图,下边这道题难不倒你!

    // 说说下边代码的执行顺序,先打印哪个?
    const fs = require(&#39;fs&#39;)
    fs.readFile(&#39;./poll.js&#39;, () => {
      setTimeout(() => console.log(&#39;setTimeout&#39;), 0)
      setImmediate(() => console.log(&#39;setImmediate&#39;))
    })

    上边这种代码逻辑,不管执行多少次,肯定都是先执行setImmediate。

    1Une combinaison dimages et de texte vous aidera à comprendre la boucle dévénements dans Nodejs

    先执行setImmediate

    因为fs各个函数的回调是放在poll队列的。当程序holding在poll队列后,出现回调立即执行。
    回调内执行setTimeout和setImmediate的函数后,check队列立即增加了回调。
    回调执行完毕,轮询检查其他队列有内容,程序结束poll队列的holding向下执行。
    check是poll阶段的紧接着的下一个。所以在向下的过程中,先执行check阶段内的回调,也就是先打印setImmediate。
    到下一轮循环,到达timers队列,检查setTimeout计时器符合条件,则定时器回调被执行。

    nextTick 与 Promise

    说完宏任务,接下来说下微任务

    • 二者都是「微队列」,执行异步微任务。
    • 二者不是事件循环的一部分,程序也不会开启额外的线程去处理相关任务。(理解:promise里发网络请求,那是网络请求开的网络线程,跟Promise这个微任务没关系)
    • 微队列设立的目的就是让一些任务「马上」、「立即」优先执行。
    • nextTick与Promise比较,nextTick的级别更高。

    nextTick表现形式

    process.nextTick(() => {})

    Promise表现形式

    Promise.resolve().then(() => {})

    如何参与事件循环?

    事件循环中,每执行一个回调前,先按序清空一次nextTick和promise。

    // 先思考下列代码的执行顺序
    setImmediate(() => {
      console.log(&#39;setImmediate&#39;);
    });
    
    process.nextTick(() => {
      console.log(&#39;nextTick 1&#39;);
      process.nextTick(() => {
        console.log(&#39;nextTick 2&#39;);
      })
    })
    
    console.log(&#39;global&#39;);
    
    
    Promise.resolve().then(() => {
      console.log(&#39;promise 1&#39;);
      process.nextTick(() => {
        console.log(&#39;nextTick in promise&#39;);
      })
    })

    最终顺序:

    • global

    • nextTick 1

    • nextTick 2

    • promise 1

    • nextTick in promise

    • setImmediate

    两个问题:

    基于上边的说法,有两个问题待思考和解决:

    • 每走一个异步宏任务队列就查一遍nextTick和promise?还是每执行完 宏任务队列里的一个回调函数就查一遍呢?

    • 如果在poll的holding阶段,插入一个nextTick或者Promise的回调,会立即停止poll队列的holding去执行回调吗?

    Une combinaison dimages et de texte vous aidera à comprendre la boucle dévénements dans Nodejs

    上边两个问题,看下边代码的说法

    setTimeout(() => {
      console.log(&#39;setTimeout 100&#39;);
      setTimeout(() => {
        console.log(&#39;setTimeout 100 - 0&#39;);
        process.nextTick(() => {
          console.log(&#39;nextTick in setTimeout 100 - 0&#39;);
        })
      }, 0)
      setImmediate(() => {
        console.log(&#39;setImmediate in setTimeout 100&#39;);
        process.nextTick(() => {
          console.log(&#39;nextTick in setImmediate in setTimeout 100&#39;);
        })
      });
      process.nextTick(() => {
        console.log(&#39;nextTick in setTimeout100&#39;);
      })
      Promise.resolve().then(() => {
        console.log(&#39;promise in setTimeout100&#39;);
      })
    }, 100)
    
    const fs = require(&#39;fs&#39;)
    fs.readFile(&#39;./1.poll.js&#39;, () => {
      console.log(&#39;poll 1&#39;);
      process.nextTick(() => {
        console.log(&#39;nextTick in poll ======&#39;);
      })
    })
    
    setTimeout(() => {
      console.log(&#39;setTimeout 0&#39;);
      process.nextTick(() => {
        console.log(&#39;nextTick in setTimeout&#39;);
      })
    }, 0)
    
    setTimeout(() => {
      console.log(&#39;setTimeout 1&#39;);
      Promise.resolve().then(() => {
        console.log(&#39;promise in setTimeout1&#39;);
      })
      process.nextTick(() => {
        console.log(&#39;nextTick in setTimeout1&#39;);
      })
    }, 1)
    
    setImmediate(() => {
      console.log(&#39;setImmediate&#39;);
      process.nextTick(() => {
        console.log(&#39;nextTick in setImmediate&#39;);
      })
    });
    
    process.nextTick(() => {
      console.log(&#39;nextTick 1&#39;);
      process.nextTick(() => {
        console.log(&#39;nextTick 2&#39;);
      })
    })
    
    console.log(&#39;global ------&#39;);
    
    Promise.resolve().then(() => {
      console.log(&#39;promise 1&#39;);
      process.nextTick(() => {
        console.log(&#39;nextTick in promise&#39;);
      })
    })
    
    /** 执行顺序如下
    global ------
    nextTick 1
    nextTick 2
    promise 1
    nextTick in promise
    setTimeout 0 // 解释问题1. 没有上边的nextTick和promise,setTimeout和setImmediate的顺序不一定,有了以后肯定是0先开始。
    // 可见,执行一个队列之前,就先检查并执行了nextTick和promise微队列
    nextTick in setTimeout
    setTimeout 1
    nextTick in setTimeout1
    promise in setTimeout1
    setImmediate
    nextTick in setImmediate
    poll 1
    nextTick in poll ======
    setTimeout 100
    nextTick in setTimeout100
    promise in setTimeout100
    setImmediate in setTimeout 100
    nextTick in setImmediate in setTimeout 100
    setTimeout 100 - 0
    nextTick in setTimeout 100 - 0
     */

    以上代码执行多次,顺序不变,setTimeout和setImmediate的顺序都没变。

    执行顺序及具体原因说明如下:

    • global :主线程同步任务,率先执行没毛病

    • nextTick 1:执行异步宏任务之前,清空异步微任务,nextTick优先级高,先行一步

    • nextTick 2:执行完上边这句代码,又一个nextTick微任务,立即率先执行

    • promise 1:执行异步宏任务之前,清空异步微任务,Promise的优先级低,所以在nextTick完了以后立即执行

    • nextTick in promise:清空Promise队列的过程中,遇到nextTick微任务,立即执行、清空

    • setTimeout 0: 解释第一个问题. 没有上边的nextTick和promise,只有setTimeout和setImmediate时他俩的执行顺序不一定。有了以后肯定是0先开始。可见,执行一个宏队列之前,就先按顺序检查并执行了nextTick和promise微队列。等微队列全部执行完毕,setTimeout(0)的时机也成熟了,就被执行。

    • nextTick in setTimeout:执行完上边这句代码,又一个nextTick微任务,立即率先执行 【这种回调函数里的微任务,我不能确定是紧随同步任务执行的;还是放到微任务队列,等下一个宏任务执行前再清空的他们。但是顺序看上去和立即执行他们一样。不过我比较倾向于是后者:先放到微任务队列等待,下一个宏任务执行前清空他们。】

    • setTimeout 1:因为执行微任务耗费时间,导致此时timers里判断两个0和1的setTimeout计时器已经结束,所以两个setTimeout回调都已加入队列并被执行

    • nextTick in setTimeout1:执行完上边这句代码,又一个nextTick微任务,立即率先执行 【可能是下一个宏任务前清空微任务】

    • promise in setTimeout1:执行完上边这句代码,又一个Promise微任务,立即紧随执行 【可能是下一个宏任务前清空微任务】

    • setImmediate:poll队列回调时机未到,先行向下到check队列,清空队列,立即执行setImmediate回调

    • nextTick in setImmediate:执行完上边这句代码,又一个nextTick微任务,立即率先执行 【可能是下一个宏任务前清空微任务】

    • poll 1:poll队列实际成熟,回调触发,同步任务执行。

    • nextTick in poll :执行完上边这句代码,又一个nextTick微任务,立即率先执行 【可能是下一个宏任务前清空微任务】

    • setTimeout 100:定时器任务到达时间,执行回调。并在回调里往微任务推入了nextTick、Promise,往宏任务的check里推入了setImmediate的回调。并且也开启了计时器线程,往timers里增加了下一轮回调的可能。

    • nextTick in setTimeout100:宏任务向下前,率先执行定时器回调内新增的微任务-nextTick 【这里就能确定了,是下一个宏任务前清空微任务的流程】

    • promise in setTimeout100:紧接着执行定时器回调内新增的微任务-Promise 【清空完nextTick清空Promise的顺序】

    • setImmediate in setTimeout 100:这次setImmediate比setTimeout(0)先执行的原因是:流程从timers向后走到check队列,已经有了setImmediate的回调,立即执行。

    • nextTick in setImmediate in setTimeout 100:执行完上边这句代码,又一个nextTick微任务,下一个宏任务前率先清空微任务

    • setTimeout 100 - 0:轮询又一次回到timers,执行100-0的回调。

    • nextTick in setTimeout 100 - 0:执行完上边这句代码,又一个nextTick微任务,下一个宏任务前率先清空微任务。

    扩展:为什么有了setImmediate还要有nextTick和Promise?

    一开始设计的时候,setImmediate充当了微队列的作用(虽然他不是)。设计者希望执行完poll后立即执行setImmediate(当然现在也确实是这么表现的)。所以起的名字叫Immediate,表示立即的意思。 但是后来问题是,poll里可能有N个任务连续执行,在执行期间想要执行setImmediate是不可能的。因为poll队列不停,流程不向下执行。

    于是出现nextTick,真正的微队列概念。但此时,immediate的名字被占用了,所以名字叫nextTick(下一瞬间)。事件循环期间,执行任何一个队列之前,都要检查他是否被清空。其次是Promise。

    面试题

    最后,检验学习成果的面试题来了

    async function async1() {
      console.log(&#39;async start&#39;);
      await async2();
      console.log(&#39;async end&#39;);
    }
    
    async function async2(){
      console.log(&#39;async2&#39;);
    }
    console.log(&#39;script start&#39;);
    
    setTimeout(() => {
      console.log(&#39;setTimeout 0&#39;);
    }, 0)
    
    setTimeout(() => {
      console.log(&#39;setTimeout 3&#39;);
    }, 3)
    
    setImmediate(() => {
      console.log(&#39;setImmediate&#39;);
    })
    
    process.nextTick(() => {
      console.log(&#39;nextTick&#39;);
    })
    
    async1();
    
    new Promise((res) => {
      console.log(&#39;promise1&#39;);
      res();
      console.log(&#39;promise2&#39;);
    }).then(() => {
      console.log(&#39;promise 3&#39;);
    });
    
    console.log(&#39;script end&#39;);
    
    // 答案如下
    // -
    // -
    // -
    // -
    // -
    // -
    // -
    // -
    // -
    // -
    // -
    // -
    
    
    
    
    
    
    /**
    script start
    async start
    async2
    promise1
    promise2
    script end
    
    nextTick
    async end
    promise 3
    
    // 后边这仨的运行顺序就是验证你电脑运算速度的时候了。
    速度最好(执行上边的同步代码 + 微任务 + 计时器运算用了不到0ms):
    setImmediate
    setTimeout 0
    setTimeout 3
    
    速度中等(执行上边的同步代码 + 微任务 + 计时器运算用了0~3ms以上):
    setTimeout 0
    setImmediate
    setTimeout 3
    
    速度较差(执行上边的同步代码 + 微任务 + 计时器运算用了3ms以上):
    setTimeout 0
    setTimeout 3
    setImmediate
    */

    思维脑图 - Node生命周期核心阶段

    1Une combinaison dimages et de texte vous aidera à comprendre la boucle dévénements dans Nodejs

    Une combinaison dimages et de texte vous aidera à comprendre la boucle dévénements dans Nodejs

    更多编程相关知识,请访问:编程视频!!

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer