Maison >interface Web >js tutoriel >Explication détaillée du mécanisme de boucle d'événements dans nodejs

Explication détaillée du mécanisme de boucle d'événements dans nodejs

青灯夜游
青灯夜游avant
2021-04-29 10:47:252599parcourir

Cet article vous guidera à travers le mécanisme de boucle d'événements dans node. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer. J'espère qu'il sera utile à tout le monde.

Explication détaillée du mécanisme de boucle d'événements dans nodejs

Le développement front-end est indissociable de JavaScript. JavaScript est un langage front-end Web principalement utilisé dans le développement Web et analysé et exécuté par le navigateur. Le rôle de js ne se limite pas seulement au développement front-end, il peut également être utilisé pour le développement côté serveur - nodejs. En tant que personne front-end avec des idéaux et des ambitions, si vous souhaitez élargir vos horizons et maîtriser un langage de développement côté serveur, nodejs est un très bon choix.

Recommandations associées : "Tutoriel nodejs "

Parce que vous maîtrisez la méthode de développement js, il est facile de démarrer avec node, ainsi que l'outil de gestion de packages npm améliore considérablement l'expérience de développement. Nodejs est célèbre pour sa méthode de travail d'E/S asynchrone non bloquante, et son mécanisme de traitement est appelé boucle d'événements.

Comprendre le mécanisme de boucle d'événements du nœud permet de mieux comprendre la méthode de traitement des événements du nœud et le calendrier d'exécution des événements asynchrones. Cet article explique principalement le mécanisme de boucle d'événements de nodejs, jetant les bases de l'apprentissage ultérieur du nœud.

1. nœud VS javascript

Comme mentionné précédemment, Javascript est un langage frontal Web, principalement utilisé dans le développement Web, et est analysé et exécuté par le navigateur. , tandis que node .js est un environnement d'exécution JavaScript basé sur le moteur Chrome V8. Par conséquent, nodejs n'est pas un langage, une bibliothèque ou un framework, mais un environnement d'exécution js. En termes simples, le nœud peut analyser et exécuter du code js. Dans le passé, seuls les navigateurs pouvaient analyser et exécuter JS, mais désormais, Node peut faire fonctionner JS complètement sans le navigateur.

Il existe de nombreuses différences entre node.js et le navigateur js. Par exemple, js dans le navigateur inclut ecmascript, BOM et DOM, mais js dans nodejs n'a pas de BOM, DOM et uniquement emcscript. Et l'environnement d'exécution js de node fournit des API d'opération au niveau du serveur pour js, telles que : la lecture et l'écriture de fichiers, la construction de services réseau, la communication réseau, le serveur http, etc. La plupart de ces API sont regroupées dans des modules de base. De plus, le mécanisme de boucle d'événements du nœud est différent du mécanisme de boucle d'événements du navigateur js.

2. Boucle d'événements JavaScript

Tout le monde est déjà très clair sur la boucle d'événements js dans le navigateur À des fins de comparaison, je la mentionnerai brièvement ici.

Explication détaillée du mécanisme de boucle dévénements dans nodejs

(Cité du discours de Philip Roberts "Au secours, je suis coincé dans une boucle d'événements")

Lorsque js est exécuté, les tâches synchrones et asynchrones entrent respectivement dans différents environnements d'exécution. Les tâches synchrones entrent dans le thread principal, c'est-à-dire la pile d'exécution principale, et les tâches asynchrones (requêtes ajax, settimeout, setinterval, poromise.resolve(), etc.) entrez dans la file d’attente des tâches. Différentes tâches asynchrones seront poussées dans différentes files d'attente de tâches, telles que les requêtes ajax, settimeout, setinterval, etc. Ces tâches seront poussées dans la file d'attente des tâches macro (Macro Task), tandis que la fonction Promise sera poussée dans la file d'attente des micro-tâches ( Microtâche). Le processus global de la boucle d'événements est le suivant :

  • Lorsque le code synchrone est exécuté, la pile d'exécution principale devient vide et les préparatifs commencent pour exécuter des tâches asynchrones.

  • Le thread principal vérifiera si la file d'attente des microtâches est vide. Si elle n'est pas vide, il parcourra toutes les microtâches de la file d'attente pour les exécuter, effacera la file d'attente des microtâches, puis vérifiez la file d’attente des tâches de macro. Si la file d'attente des microtâches est vide, passez directement à l'étape suivante.

  • Le thread principal parcourt la file d'attente des tâches macro et exécute la première tâche macro dans la file d'attente des tâches macro. S'il rencontre une tâche macro ou une micro-tâche pendant l'exécution, il continuera à traiter. Placez-le dans la file d'attente des tâches correspondante. Chaque fois qu'une macro-tâche est exécutée, la file d'attente des micro-tâches doit être parcourue et effacée

  • Effectuer les opérations de rendu et mettre à jour la vue

  • Démarrez la boucle d'événements suivante et répétez les étapes ci-dessus jusqu'à ce que les deux files d'attente de tâches soient effacées

Afin d'approfondir l'impact, prenons un petit exemple et regardez le code suivant Ce qui sera généré :

    var le=Promise.resolve(2);
    console.log(le)
    console.log('3')
    Promise.resolve().then(()=>{
    console.log('Promise1')  
    setTimeout(()=>{
        console.log('setTimeout2')
    },0)
    })
    setTimeout(()=>{
    console.log('setTimeout1')
    Promise.resolve().then(()=>{
        console.log('Promise2')    
    })
    },0);

Utilisez le processus de boucle d'événements ci-dessus pour analyser :

  • Le code d'exécution du processus principal js sera exécuté immédiatement lorsqu'il rencontrera Promise. solve(2), en changeant 2 en un objet de promesse, puis console.log(le) imprime la variable chier, print----->Promise {: 2}
  • console.log('3') , print----->3
  • Ensuite, continuez l'exécution et rencontrez Promise.resolve().then, qui est une fonction de microtâche asynchrone, poussée vers la pile de microtâches
  • Une fonction rencontre setTimeout et est poussée vers la file d'attente des tâches de macro. Le processus principal est maintenant vide
  • Vérifiez la file d'attente des micro-tâches et recherchez Promise.resolve().then, alors imprimez -- --->promesse1 et rencontre à nouveau un minuteur, poussez-le jusqu'à la fin de la file d'attente des tâches macro, la file d'attente des micro-tâches est vide
  • Vérifiez la file d'attente des tâches macro, prenez la première tâche macro à exécuter, imprimez ----->setTimeout1, et rencontrez à nouveau Promise.resolve().puis, poussez-le dans la file d'attente des microtâches
  • avant de démarrer la macrotâche suivante, la microtâche sera effacée, donc après l'impression de setTimeout1, le la file d'attente des microtâches sera vérifiée, donc ---> ;promise2
  • Le prochain tour de boucle d'événements consiste à prendre la première tâche actuelle de la file d'attente des tâches macro et à l'exécuter, donc print print-----> setTimeout2. À ce stade, les files d'attente de macro-tâches et de micro-tâches ont été effacées, la boucle d'événements se termine
, le résultat de sortie est donc : Promise {: 2}, 3, promesse1, setTimeout1, promesse2, setTimeout2.

Le résultat de l'exécution dans le navigateur est le suivant :

Explication détaillée du mécanisme de boucle dévénements dans nodejs

3. Boucle d'événement de nœud

Il y a boucles d'événements totales du nœud Il y a six étapes dans une boucle d'événements, ces six étapes seront exécutées en séquence jusqu'à ce que le traitement de l'événement soit terminé. Le diagramme de séquence des six étapes est le suivant :

Explication détaillée du mécanisme de boucle dévénements dans nodejs

六个阶段分别是:

  • timers 阶段:这个阶段执行timer(setTimeout、setInterval)的回调
  • I/O callbacks 阶段:执行一些系统操作的回调(比如网络通信的错误回调);
  • idle, prepare 阶段:仅node内部使用,可忽略
  • poll 阶段:获取新的I/O事件, 适当的条件下node将阻塞在这里
  • check 阶段:执行 setImmediate() 的回调
  • close callbacks 阶段:执行 socket 的 close 事件回调,如果一个socket或handle被突然关掉(比如socket.destroy()),close事件将在这个阶段被触发

事件循环中,每当进入某一个阶段,都会从该阶段对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值,该阶段就会终止,然后检查NextTick队列和微任务队列,将其清空,之后进入下一个阶段。

这里面比较关键的是poll阶段:

  • poll队列不为空的时候,事件循环会遍历队列并同步执行回调,直到队列清空或执行回调数达到系统上限。
  • poll队列为空的时候,就会有两种情况:
    • 如果代码中存在setImmediate()回调,那么事件循环直接结束poll阶段进入check阶段来执行check队列里的回调;
    • 如果不存在setImmediate()回调,会等待回调被加入到队列中并立即执行回调,这里同样会有个超时时间设置防止一直等待下去,如果规定时间内有定时器函数进入队列,则返回到timer阶段,执行定时器回调,否则在poll阶段等待回调进入队列。

同样的举个大大的,看看以下代码会输出什么:

console.log('start')
setTimeout(() => {
  console.log('timer1')
  Promise.resolve().then(function() {
    console.log('promise1')
  })
}, 0)
setTimeout(() => {
  console.log('timer2')
  Promise.resolve().then(function() {
    console.log('promise2')
  })
}, 0)
Promise.resolve().then(function() {
  console.log('promise3')
})
console.log('end')

利用node事件循环分析呗:

  • 先执行同步任务,打印start,end
  • 进入timer阶段前,清空NextTick和micro队列,所以打印promise3
  • 进入timer阶段,打印timer1,并发现有一个微任务,立即执行微任务,打印promise1
  • 仍然在timer阶段,执行下个宏任务,打印timer2,同样遇到微任务,立即执行,打印promise2

因此输出顺序是:start,end,promise3,timer1,promise1,timer2,promise2,如果能正确回答出来说明对node的循环机制有了大体的了解,实际node输出结果确实是这样:

Explication détaillée du mécanisme de boucle dévénements dans nodejs

那如下代码会输出什么呢?

process.nextTick(function(){
    console.log(7);
});

new Promise(function(resolve){
    console.log(3);
    resolve();
    console.log(4);
}).then(function(){
    console.log(5);
});

process.nextTick(function(){
    console.log(8);
});

继续分析:

  • process.nextTick会将任务推进至nextTick队列,promise.then会把任务推至micro队列,上面提到过每次一个宏任务执行完,执行下一个宏任务之前需要清空nextTick队列和micro队列,同样的一个阶段执行完,进入下一个阶段之前也需要nextTick队列和micro队列,并且nextTick队列优先级高于micro队列
  • 先执行同步代码,打印3,4
  • 执行nextTick队列,打印7,8
  • 再执行micro队列,打印5

因此最终输出是:3,4,7,8,5,需要记住,process.nextTick 永远大于 promise.then的优先级

还有一个大家很容易混淆的点就是setTimout和setImmediate的执行时机,根据上面描述的node事件循环机制,setImmediate()应该在check阶段执行 与 而setTimeout在timer阶段执行,理论上setTimout比setImmediate先执行,看下面的代码:

setTimeout(() => console.log(1),0);
setImmediate(() => console.log(2));

执行结果是什么?1,2 还是 2,1,其实都有可能,看实际node运行的结果:

Explication détaillée du mécanisme de boucle dévénements dans nodejs

可以看到两次执行的结果不一样,为什么呢?原因在于即使setTimeout的第二个参数默认为0,但实际上,Node做不到0秒就执行其回调,最少也要4毫秒。那么进入事件循环后,如果没到4毫秒,那么timers阶段就会被跳过,从而进入check阶段执行setImmediate回调,此时输出结果是:2,1;

如果进入事件循环后,超过4毫秒(只是个大概,具体值并不确定),setTimeout的回调会出现在timer阶段的队列里,回调将被执行,之后再进入poll阶段和check阶段,此时输出结果是:1,2

那如果两者在I/O周期内调用,谁先执行呢?看一下代码:

const fs = require('fs')

fs.readFile('./test.txt', 'utf8' , (err, data) => {
  if (err) {
    console.error(err)
    return
  }
  setTimeout(() => {
    console.log('timeout');
  }, 0);
  setImmediate(() => {
    console.log('immediate');
  });
})

实际上,node中输出的结果总是immediate先输出,timeout后输出。因为I/O回调是在poll阶段执行,当回调执行完毕之后队列为空,发现存在setImmediate的回调就会进入check阶段,执行完毕后,再进入timer阶段。

四、总结

本文结合代码示例,对node的事件循环机制做了比较详细描述。通过这篇文章,应该可以了解浏览器的事件循环机制是怎样的,node的循环机制是怎样的,以及nextTick和micro队列的优先级,setTimout和setImmediate执行时机等一些容易混淆的知识点。文章中不足和不对之处,欢迎在评论区交流讨论,一起探索,谢谢。

更多编程相关知识,请访问:编程入门!!

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