Maison  >  Article  >  interface Web  >  Comprendre les minuteries dans Node.js

Comprendre les minuteries dans Node.js

青灯夜游
青灯夜游avant
2020-12-03 18:11:133231parcourir

Comprendre les minuteries dans Node.js

Recommandations associées : "tutoriel node js"

le minuteur est utilisé pour planifier les fonctions à appeler à un certain moment dans le future, Node La fonction timer dans .js implémente une API similaire à l'API timer fournie par les navigateurs Web, mais utilise une boucle d'événements pour l'implémenter. Il existe quatre méthodes associées dans Node.js

  • <.>setTimeout(callback, delay[, ...args])

  • setInterval(callback[, ...args])

  • setImmediate (callback[, ...args])

  • process.nextTick(callback[, ...args])

Le premier 2 La signification est cohérente avec celle du Web. Les deux derniers sont uniques à Node.js. L'effet semble être setTimeout(callback, 0). Il est le plus utilisé dans la programmation Node.js

Node. js ne le fait pas. L'heure exacte à laquelle les rappels sont déclenchés n'est pas garantie, et leur ordre n'est pas non plus garanti ; les rappels seront appelés aussi près que possible de l'heure spécifiée ; setTimeout Lorsque le délai est supérieur à 2147483647 ou inférieur à 1, le délai sera défini sur 1 et le délai non entier sera tronqué à un nombre entier


Ordre d'exécution étrange

Regardez un exemple, en utilisant plusieurs méthodes pour imprimer un nombre de manière asynchrone

setImmediate(console.log, 1);
setTimeout(console.log, 1, 2);
Promise.resolve(3).then(console.log);
process.nextTick(console.log, 4);
console.log(5);
imprimera 5 4 3 2 1 ou 5 4 3 1 2


Synchrone et asynchrone

Le cinquième ligne Il est exécuté de manière synchrone, et les autres sont asynchrones

setImmediate(console.log, 1);
setTimeout(console.log, 1, 2);
Promise.resolve(3).then(console.log);
process.nextTick(console.log, 4);
/****************** 同步任务和异步任务的分割线 ********************/
console.log(5);
Alors imprimez 5 en premier, ce qui est facile à comprendre. Le reste est des opérations asynchrones. Dans quel ordre Node.js s'exécute-t-il ?


boucle d'événements

Node.js initialisera l'interrogation des événements après le démarrage, puis commencera le traitement. boucle d'événements. Il existe une telle image sur le site officiel pour présenter la séquence de fonctionnement de la boucle d'événements

┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘

Chaque étape de la boucle d'événements a une file d'attente de tâches. Lorsque la boucle d'événements entre dans une étape donnée, la file d'attente de tâches de cette étape sera. être exécuté. , il ne passera pas à l'étape suivante jusqu'à ce que la file d'attente soit vidée ou que le rappel exécuté atteigne la limite supérieure du système. Lorsque toutes les étapes sont exécutées en séquence, on dit que la boucle d'événements a terminé un tick

<🎜. >Les opérations asynchrones sont placées ensuite Dans un tick de boucle d'événement, process.nextTick est exécuté avant d'entrer dans le tick de boucle d'événement suivant, il doit donc être avant les autres opérations asynchrones

setImmediate(console.log, 1);
setTimeout(console.log, 1, 2);
Promise.resolve(3).then(console.log);
/****************** 下次 event loop tick 分割线 ********************/
process.nextTick(console.log, 4);
/****************** 同步任务和异步任务的分割线 ********************/
console.log(5);

Tâches principales de chaque étape

  • timers

     : Exécuter les rappels setTimeout, setInterval

  • rappels en attente

     : Exécuter les E/S (fichier, réseau, etc.) rappels

  • inactif, préparer

     : Appelé uniquement en interne par le système

  • sondage

     : Obtenez de nouveaux événements d'E/S, exécutez les rappels pertinents, dans des conditions appropriées, bloquez le nœud

  • check

     : le rappel setImmediate est exécuté à ce stade

  • close callbacks

     : Exécute des rappels d'événements de fermeture tels que les sockets

  • La plupart des tâches asynchrones du développement quotidien sont traitées dans les minuteries, sondage , et vérifiez les étapes


timers


Node.js vérifiera s'il y a un minuteur expiré dans l'étape timers. S'il existe, le rappel sera placé dans l'étape timers. la file d'attente du minuteur pour attendre l'exécution. Node.js utilise un seul thread et est limité. En raison de l'état d'inactivité du thread principal et de l'influence d'autres processus sur la machine, il n'y a aucune garantie que le minuteur sera exécuté conformément au timer. heure précise

Il existe deux principaux types de minuteries


    Immédiat
  • Délai d'attente
  • Le rappel de minuterie de type immédiat sera appelé dans la phase
check

Le minuteur Timeout appellera le rappel dès que possible après l'expiration du délai défini, mais

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

setImmediate(() => {
  console.log('immediate');
});
s'il est exécuté plusieurs fois, vous. constatera que l'ordre d'impression est différent


sondage

La phase de sondage comporte principalement deux tâches

    Calculer le temps qui devrait bloquer et interroger les E/S
  • Ensuite, traiter les événements dans la file d'attente d'interrogation
  • Lorsque l'événement boucle Lors de l'entrée dans la phase d'interrogation et qu'il y a pas de minuterie programmée

Si la file d'attente d'interrogation n'est pas vide, la boucle d'événements parcourra la file d'attente de rappel et s'exécutera de manière synchrone jusqu'à ce que la file d'attente soit épuisée ou atteigne le système ou atteigne le nombre maximum de rappels
  • Si la file d'attente d'interrogation est vide
  • S'il y a une tâche setImmediate(), la boucle d'événements entrera dans la phase
      check
    • après avoir terminé la phase poll S'il n'y a pas de tâche setImmediate(), la boucle d'événements se bloque dans l'étape
    • poll
    • en attendant que le rappel soit ajouté à la file d'attente, puis s'exécute immédiatement
  • Une fois la file d'attente
sondage

vide, la boucle d'événements vérifiera si la file d'attente du minuteur est vide. Si elle n'est pas vide, elle entrera dans le prochain tour de boucle d'événements<.>Comme mentionné ci-dessus, si dans des E/S différentes, l'ordre d'exécution de setTimeout et setImmediate ne peut pas être déterminé, mais si setTimeout et setImmediate sont dans un rappel d'E/S, setImmediate doit être exécuté en premier, car setImmediate( ) la tâche est détectée dans la phase d'interrogation, et la boucle d'événements entre directement dans la phase de vérification pour exécuter le rappel setImmediate

const fs = require('fs');
fs.readFile(__filename, () => {
  setTimeout(() => {
    console.log('timeout');
  }, 0);
  setImmediate(() => {
    console.log('immediate');
  });
});

check

Execute setImmediate callback

à ce stade

为什么 Promise.then 比 setTimeout 早一些

前端同学肯定都听说过 micoTask 和 macroTask,Promise.then 属于 microTask,在浏览器环境下 microTask 任务会在每个 macroTask 执行最末端调用

在 Node.js 环境下 microTask 会在每个阶段完成之间调用,也就是每个阶段执行最后都会执行一下 microTask 队列

setImmediate(console.log, 1);
setTimeout(console.log, 1, 2);
/****************** microTask 分割线 ********************/
Promise.resolve(3).then(console.log); // microTask 分割线
/****************** 下次 event loop tick 分割线 ********************/
process.nextTick(console.log, 4);
/****************** 同步任务和异步任务的分割线 ********************/
console.log(5);

setImmediate VS process.nextTick

setImmediate 听起来是立即执行,process.nextTick 听起来是下一个时钟执行,为什么效果是反过来的?这就要从那段不堪回首的历史讲起

最开始的时候只有 process.nextTick 方法,没有 setImmediate 方法,通过上面的分析可以看出来任何时候调用 process.nextTick(),nextTick 会在 event loop 之前执行,直到 nextTick 队列被清空才会进入到下一 event loop,如果出现 process.nextTick 的递归调用程序没有被正确结束,那么 IO 的回调将没有机会被执行

const fs = require('fs');

fs.readFile('a.txt', (err, data) => {
	console.log('read file task done!');
});

let i = 0;
function test(){
	if(i++ < 999999) {
  	console.log(`process.nextTick ${i}`);
    process.nextTick(test);
  }
}
test();

执行程序将返回

nextTick 1
nextTick 2
...
...
nextTick 999999
read file task done!

于是乎需要一个不这么 bug 的调用,setImmediate 方法出现了,比较令人费解的是在 process.nextTick 起错名字的情况下,setImmediate 也用了一个错误的名字以示区分。。。

那么是不是编程中应该杜绝使用 process.nextTick 呢?官方推荐大部分时候应该使用 setImmediate,同时对 process.nextTick 的最大调用堆栈做了限制,但 process.nextTick 的调用机制确实也能为我们解决一些棘手的问题

  • 允许用户在 even tloop 开始之前 处理异常、执行清理任务

  • 允许回调在调用栈 unwind 之后,下次 event loop 开始之前执行

一个类继承了 EventEmitter,而且想在实例化的时候触发一个事件

const EventEmitter = require(&#39;events&#39;);
const util = require(&#39;util&#39;);

function MyEmitter() {
  EventEmitter.call(this);
  this.emit(&#39;event&#39;);
}
util.inherits(MyEmitter, EventEmitter);

const myEmitter = new MyEmitter();
myEmitter.on(&#39;event&#39;, () => {
  console.log('an event occurred!');
});

在构造函数执行 this.emit('event') 会导致事件触发比事件回调函数绑定早,使用 process.nextTick 可以轻松实现预期效果

const EventEmitter = require('events');
const util = require('util');

function MyEmitter() {
  EventEmitter.call(this);

  // use nextTick to emit the event once a handler is assigned
  process.nextTick(() => {
    this.emit('event');
  });
}
util.inherits(MyEmitter, EventEmitter);

const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
  console.log('an event occurred!');
});

更多编程相关知识,请访问:编程教学!!

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