ホームページ  >  記事  >  ウェブフロントエンド  >  Node.js のタイマーを理解する

Node.js のタイマーを理解する

青灯夜游
青灯夜游転載
2020-12-03 18:11:133152ブラウズ

Node.js のタイマーを理解する

関連する推奨事項: 「node js チュートリアル

タイマーは、ノード内の特定の時点で呼び出される関数をスケジュールするために使用されます。今後、Node .js のタイマー関数は、Web ブラウザーが提供するタイマー API に似た API を実装しますが、イベント ループを使用して実装します。Node.js

  • には 4 つの関連メソッドがあります。

    setTimeout(コールバック, 遅延[, ...args])

  • ##setInterval(コールバック[, ...args])

  • #setImmediate (callback[, ...args])
  • process.nextTick(callback[, ...args])
  • First 2 つの意味は Web 上の意味と一致しています。最後の 2 つは Node.js に固​​有です。効果は setTimeout(callback, 0) のようです。Node.js プログラミングで最もよく使用されます

Node .js では、コールバックが呼び出される正確な時刻や順序は保証されず、コールバックは指定された時刻にできるだけ近い時刻に呼び出されます。 setTimeout 遅延が 2147483647 より大きいか 1 未満の場合、遅延は 1 に設定され、整数以外の遅延は整数に切り捨てられます。


実行順序がおかしいです。

たとえば、いくつかのメソッドを使用して数値を非同期に出力します。

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

は 5 4 3 2 1 または 5 4 3 1 2


同期 & 非同期

5 番目を出力します。行 同期的に実行され、他はすべて非同期です

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

なので、最初に 5 を出力します。これは理解しやすいです。残りは非同期操作です。Node.js はどのような順序で実行されますか?


イベント ループ

Node.js は起動後にイベント ポーリングを初期化し、その過程で非同期呼び出し、タイマー スケジュール、process.nextTick() を処理してから処理を開始する場合があります。イベントループ。イベントループの動作シーケンスを紹介する画像が公式サイトにあります

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

イベントループの各ステージにはタスクキューがあり、イベントループが特定のステージに入ると、そのステージのタスクキューはは、キューがクリアされるか、実行されたコールバックがシステムの上限に達するまで、次のステージに移動しません。すべてのステージが順番に実行されると、イベント ループはティックを完了したと言われます

非同期操作は次に配置されます。イベント ループ ティックでは、process.nextTick は次のイベント ループ ティックに入る前に実行されるため、他の非同期操作の前にある必要があります

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);

各ステージの主なタスク

  • timers

    : setTimeout、setInterval コールバックを実行します。

  • pending callbacks

    : I/O を実行します (ファイル、ネットワークなど) .) callbacks

  • idle、prepare

    : システムによって内部的にのみ呼び出されます

  • poll

    : 新しい I/O イベントを取得し、関連するコールバックを実行し、適切な条件下でノードをブロックします。

  • check

    : この段階で setImmediate コールバックが実行されます

  • close callbacks

    : ソケットなどの close イベント コールバックを実行します。

  • 日常の開発におけるほとんどの非同期タスクはタイマーで処理されます。ポーリングとステージの確認


timers


Node.js は、タイマー フェーズで期限切れのタイマーがあるかどうかを確認します。存在する場合は、コールバックが配置されます。実行を待機するタイマー キュー。Node.js はシングル スレッドを使用し、制限されています。メイン スレッドのアイドル状態やマシン上の他のプロセスの影響により、タイマーが指定に従って実行されるという保証はありません。

タイマーには主に 2 つのタイプがあります


    即時
  • タイムアウト
  • 即時型タイマーのコールバックは、
check

フェーズで呼び出されます。タイムアウト タイマーは、設定時間が経過した後、できるだけ早くコールバックを呼び出します。ただし、

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

setImmediate(() => {
  console.log('immediate');
});
複数回実行された場合


#投票ステージには主に 2 つのタスクがあります

印刷の順序が異なることがわかります。 I/Oをブロックしてポーリングする必要があります

  • その後、ポーリングキュー内のイベントを処理します

  • イベントループ時ポーリングフェーズに入ったときスケジュールされたタイマーではありません

ポーリング キューが空でない場合、イベント ループはコールバック キューを循環し、キューが使い果たされるか、システムに到達するか、コールバックの最大数に達するまで同期的に実行されます

ポーリング キューが空の場合
    #setImmediate() タスクがある場合、イベント ループは
  • poll
  • の終了後に
  • check
      フェーズに入ります。フェーズ
    • setImmediate() タスクがない場合、イベント ループは poll ステージでブロックされ、コールバックがキューに追加されるのを待ち、すぐに実行されます
    • poll
    キューが空になると、イベント ループはタイマー キューが空かどうかを確認します。空でない場合は、次のラウンドのイベントに入ります。前述のとおり、異なる I/O の場合、 setTimeout と setImmediate の実行順序は決定できませんが、 setTimeout と setImmediate が I/O コールバック内にある場合は、 setImmediate が最初に実行される必要があります。 setImmediate() タスクはポーリング フェーズで検出され、イベント ループは直接チェック フェーズに入り、setImmediate コールバックを実行します。
const fs = require('fs');
fs.readFile(__filename, () => {
  setTimeout(() => {
    console.log('timeout');
  }, 0);
  setImmediate(() => {
    console.log('immediate');
  });
});
check

この段階で setImmediate コールバックを実行します。

为什么 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!');
});

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

以上がNode.js のタイマーを理解するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcnblogs.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。