Tự học Node.js cơ bản để đi phỏng vấn (13 bài) – 4 – Event loop

30 min


1083
1.8k share, 1083 points

Giới thiệu

Vòng lặp sự kiện (event loop) cho phép mô hình I/O không chặn của Node, đây là chìa khóa giúp Node có khả năng mở rộng quy mô khi tải (như bạn đã thấy trong ” Khám phá các khái niệm cơ bản của Node.js” ). Trong hướng dẫn này, bạn tìm hiểu thêm về vòng lặp sự kiện, bao gồm các giai đoạn được xác định rõ theo một thứ tự cụ thể – trong vòng lặp sự kiện.

Cụ thể, bao gồm:

  • Các giai đoạn của vòng lặp sự kiện: chúng làm gì, thứ tự chạy và cách callback chạy trong các giai đoạn đó.
  • Cách vạch ra vòng lặp sự kiện và nhiều cách khác nhau để bạn có thể viết mã chạy trong vòng lặp sự kiện, bao gồm:
    • Gọi lại tác vụ được sử dụng với timer. Tôi đi vào chi tiết về những giai đoạn mà các callback này chạy.
    • Các callback microtask chạy ngay sau giai đoạn mà chúng được tạo.
  • API sự kiện, bao gồm cách sử dụng API hướng sự kiện của Node và cách tạo sự kiện tùy chỉnh của riêng bạn
  • Cách sử dụng Stream để xử lý file. 

Hầu hết mã bạn viết với tư cách là dev Node là các callback . Cho đến khi bạn hiểu loại callback nào chạy trong giai đoạn nào, callback của bạn sẽ không chạy theo dự đoán hoặc theo thứ tự bạn muốn.

Sau khi hoàn thành, bạn sẽ nắm rõ cách thức hoạt động của event loop. Bạn cũng sẽ biết cách tạo ra các event loop cho chính mình.

Tổng quan về các giai đoạn vòng lặp sự kiện

Minh họa giai đoạn của vòng lặp sự kiện:

Vòng đời ứng dụng nút

Màu xám là các giai đoạn Đang chờ xử lý, Không sử dụng/Chuẩn bị và Đóng của vòng lặp sự kiện vì chúng được sử dụng nội bộ bởi NodeJS. Code (app) của chúng ta chỉ chạy trong trong các phần màu vàng : mainline, timers , poll, check

Trước vòng lặp sự kiện: node mynodeapp

Khi bạn khởi động Node, bạn cung cấp cho nó tên của tệp JavaScript sẽ được xử lý, chẳng hạn như mynodeapp. Sau đó, nút xử lý tập lệnh đó và chuyển tập lệnh đó sang V8 để được ngữ cảnh hóa , liên quan đến việc chuyển đổi mã JavaScript của bạn thành các bản sao C++ có thể chạy được của nó và các nội dung khác nằm ngoài phạm vi của khóa học này (xem thêm trong tài liệu về Node.js hoặc câu hỏi StackOverflow này nếu bạn muốn tìm hiểu thêm).

Sau đó, mã JavaScript được ngữ cảnh hóa của bạn sẽ được chạy trên luồng Node . Từ giờ trở đi, tôi sẽ đề cập đến luồng chạy công cụ V8, vòng lặp sự kiện và tất cả JavaScript được ngữ cảnh hóa (bao gồm mã JavaScript trong API Node và các mô-đun của bên thứ ba) là Node thread .

Callback queue

Mỗi giai đoạn có một Callback queue FIFO được thực hiện trong giai đoạn đó. 

Nói chung, bất kỳ logic cụ thể nào đều được thực thi vào đầu giai đoạn. Sau đó, các callback trong hàng đợi sẽ chạy cho đến khi hàng đợi trống hoặc đạt đến giới hạn hệ thống.

Microtasks

Các Microtasks thực thi ngay sau mainline và sau mỗi giai đoạn của vòng lặp sự kiện.

Nếu bạn đã quen thuộc với vòng lặp sự kiện JavaScript , thì bạn sẽ cảm thấy quen thuộc với các vi tác vụ, hoạt động theo cách tương tự trong Node. Nếu bạn muốn xem lại vòng lặp sự kiện và hàng đợi vi tác vụ, hãy xem Vòng lặp sự kiện trên HTML Living Standard

Với NodeJs, các Microtasks là các callback từ:

  • process.nextTick()
  • then()trình xử lý cho các Promise() đã resolve hoặc reject

Ngay sau khi mainline kết thúc và sau mỗi giai đoạn của vòng lặp sự kiện, các callback microtask sẽ chạy.

Trong các ví dụ sau của hướng dẫn này, bạn sử dụng lệnh callback microtask để làm rõ các giai đoạn của vòng lặp sự kiện.

Timers phase

Mọi callback hết hạn đều chạy trong giai đoạn này của vòng lặp sự kiện.

Hai loại bộ hẹn giờ là:

Timer Immediate là một đối tượng NodeJs chạy ngay lập tức trong giai đoạn Kiểm tra tiếp theo.

Timeout là một đối tượng NodeJs chạy gọi lại càng sớm càng tốt sau khi bộ hẹn giờ hết hạn. Bạn cung cấp lệnh gọi lại và tham delay số tùy chọn (giá trị tính bằng mili giây) khi bạn tạo bộ hẹn giờ, mặc định là 1ms nếu bạn không cung cấp giá trị hoặc nếu bạn chỉ định giá trị bằng 0.

Khi bộ hẹn giờ hết hạn, callback được gọi trong giai đoạn Bộ hẹn giờ tiếp theo của vòng lặp sự kiện. Giá trị này có thể muộn hơn giá trị thời gian chờ thực tế, tùy thuộc vào thời điểm chạy giai đoạn Bộ hẹn giờ tiếp theo.

Có hai loại bộ Timeout đếm thời gian:

  • Interval: Được tạo bằng setInterval(). Cuộc gọi lại chạy một lần mỗi khi hết giờ (sau delayms) và lặp lại chừng nào quá trình Node còn hoạt động: (1) trừ khi bạn gọi clearInterval()trước khi nó chạy lần đầu tiên hoặc (2) cho đến khi bạn gọi clearInterval()sau khi nó chạy một hoặc nhiều lần lần.
  • Timeout: Được tạo bằng setTimeout(). Sau khi delayvượt qua, cuộc gọi lại sẽ được chạy một lần, trừ khi bạn gọi clearTimeout()trước khi cuộc gọi lại có cơ hội chạy.

Khi không còn timer callback hết hạn nào để chạy, vòng lặp sự kiện sẽ chạy bất kỳ microtask nào. Sau khi chạy microtask, vòng lặp sự kiện chuyển sang giai đoạn Pending phase.

Pending phase

Một số callback cấp hệ thống được thực hiện trong giai đoạn này. Bạn không cần phải lo lắng về giai đoạn này nhưng tôi muốn bạn biết rằng nó tồn tại.

Idle and Prepare phase

Giai đoạn này chỉ được sử dụng nội bộ, như đã giải thích trong Tổng quan về các giai đoạn , vì vậy đó là tất cả những gì tôi sẽ nói về nó. Một lần nữa, chỉ cần biết nó ở đó.

Poll phase

Các callback I/O được thực hiện trong giai đoạn này , trong đó fs.readFile() lệnh gọi hàm yêu cầu bạn cung cấp một lệnh gọi lại khi thao tác I/O hoàn tất, để truyền nội dung của file:

.
.
    fs.readFile(fileName, 'utf8', function(err, fileContents) {
        if (err) throw err;
        logger.debug('File read complete. File contents length: ' + fileContents.length);
.
.
        logger.trace('fs.readFile(): END', startTime);
    });
};

Thông thường, nếu hàng đợi thăm dò trống, nó sẽ chặn và đợi bất kỳ hoạt động I/O đang hoạt động nào hoàn tất, sau đó thực hiện lệnh gọi lại của chúng ngay lập tức. Tuy nhiên, nếu bộ hẹn giờ được lên lịch thì giai đoạn thăm dò ý kiến ​​sẽ kết thúc. Bất kỳ vi nhiệm vụ nào cũng sẽ được chạy khi cần thiết và vòng lặp sự kiện sẽ chuyển sang giai đoạn kiểm tra.

Check phase

Giai đoạn này là một loại giai đoạn “hậu I/O” trong đó chỉ setImmediate()các lệnh gọi lại được thực thi. Điều này cho phép bạn chạy mã thực thi ngay khi giai đoạn thăm dò không hoạt động.

Khi hàng đợi gọi lại giai đoạn kiểm tra trống, mọi vi tác vụ sẽ chạy và vòng lặp sự kiện sẽ chuyển sang giai đoạn đóng.

Close phase

Giai đoạn này được thực hiện nếu socket hoặc handle bị đóng đột ngột (ví dụ: nếu socket.destroy() được gọi), trong trường hợp đó, sự kiện ‘đóng’ của nó được phát ra tại đây.

Vì điều kiện này không có khả năng xảy ra nên tôi sẽ bỏ qua giai đoạn này khỏi cuộc thảo luận.

Các giai đoạn vòng lặp sự kiện (event loop) chi tiết hơn

Bây giờ bạn đã thấy tổng quan về tất cả các giai đoạn, hãy xem xét sâu hơn về ba giai đoạn mà các lệnh gọi lại thường được gọi:

  • Timers
  • Poll
  • Check

Tôi sẽ chỉ cho bạn các ví dụ về lệnh gọi lại chạy trong từng giai đoạn. Mã nguồn cho các ví dụ trong phần này bạn vui lòng inbox , tôi sẽ gửi trực tiếp qua zalo hoặc messenger.

Timers Phase callbacks

Trong giai đoạn hẹn giờ, bạn chạy lệnh gọi lại cho các bộ hẹn giờ Interval và Timeout đã hết hạn. Đầu tiên chúng ta hãy nhìn vào bộ đếm thời gian Interval.

setInterval()

Nếu bạn cần một vòng lặp trên bộ hẹn giờ, thì ta dùng bộ hẹn giờ Interval:

Ví dụ 1. setInterval()

01 const logger = require('../common/logger');
02 const ITERATIONS_MAX = 5;
03 let iteration = 0;
04 logger.info('START', 'MAINLINE');
05 const timeout = setInterval(() => {
06     logger.info('START: setInterval', 'TIMERS PHASE');
07     if (iteration >= ITERATIONS_MAX) {
08         logger.info('Max interval count exceeded. Goodbye.', 'TIMERS PHASE');
09         // Kill the interval timer
10        clearInterval(timeout);
11     }
12     iteration++;
13     logger.info('END: setInterval', 'TIMERS PHASE');
14 }, 10);
15 logger.info('END', 'MAINLINE');

setInterval()tạo bộ đếm thời gian lặp lại 5 lần và chạy không sớm hơn 10 mili giây một lần (được delay chỉ định trên dòng 14). Mỗi khi bộ đếm thời gian hết hạn, cuộc gọi lại (dòng 6-13) sẽ được chạy cho đến khi clearInterval()được gọi (dòng 10).

Output example1.js trông như thế này:

$ node example1
1530377268828:INFO: mainline: START
1530377268830:INFO: mainline: END
1530377268843:INFO: TIMERS PHASE: START: setInterval
1530377268844:INFO: TIMERS PHASE: END: setInterval
1530377268856:INFO: TIMERS PHASE: START: setInterval
1530377268856:INFO: TIMERS PHASE: END: setInterval
1530377268866:INFO: TIMERS PHASE: START: setInterval
1530377268866:INFO: TIMERS PHASE: END: setInterval
1530377268878:INFO: TIMERS PHASE: START: setInterval
1530377268879:INFO: TIMERS PHASE: END: setInterval
1530377268891:INFO: TIMERS PHASE: START: setInterval
1530377268892:INFO: TIMERS PHASE: END: setInterval
1530377268903:INFO: TIMERS PHASE: START: setInterval
1530377268903:INFO: TIMERS PHASE: Max interval count exceeded. Goodbye.
1530377268904:INFO: TIMERS PHASE: END: setInterval
$

Để xóa bộ đếm thời gian, bạn phải giữ một tham chiếu đến Timeout đối tượng được trả về từ setInterval() lệnh gọi và chuyển tham chiếu đó đến clearInterval() hàm (dòng 10). Nếu bạn không gọi clearInterval(), bộ đếm thời gian sẽ chạy mãi mãi.

setTimeout()

Hẹn setTimeout() giờ là hẹn giờ một lần. Bạn tạo một cái như thế này (dòng 8-10):

Ví dụ 2. setTimeout() ví dụ hiển thị gọi lại giai đoạn hẹn giờ của nó

01 const logger = require('../common/logger');
02 const ITERATIONS_MAX = 5;
03 let iteration = 0;
04 logger.info('START', 'MAINLINE');
05 const timeout = setInterval(() => {
06     logger.info('START: setInterval', 'TIMERS PHASE');
07     if (iteration < ITERATIONS_MAX) {
08         setTimeout(() => {
09             logger.info('setInterval.setTimeout', 'TIMERS PHASE');
10         });
11     } else {
12         logger.info('Max interval count exceeded. Goodbye.', 'TIMERS PHASE');
13         clearInterval(timeout);
14     }
15     iteration++;
16     logger.info('END: setInterval', 'TIMERS PHASE');
17 }, 0);
18 logger.info('MAINLINE: END');

Bộ đếm thời gian một lần rất nhàm chán, vì vậy tôi đã thêm nó vào mã từ ví dụ setInterval(). Tôi đã thay đổi khoảng thời gian chờ thành 0 và chưa đặt delay, vì vậy cuộc gọi sẽ hết hạn sau 1 mili giây sau khi được tạo.

Khi bạn chạy, example2.js bạn sẽ thấy output như thế này:

$ node example2
1530377605443:INFO: MAINLINE: START
1530377605446:INFO: MAINLINE: END
1530377605447:INFO: TIMERS PHASE: START: setInterval
1530377605447:INFO: TIMERS PHASE: END: setInterval
1530377605449:INFO: TIMERS PHASE: setInterval.setTimeout
1530377605449:INFO: TIMERS PHASE: START: setInterval
1530377605449:INFO: TIMERS PHASE: END: setInterval
1530377605450:INFO: TIMERS PHASE: setInterval.setTimeout
1530377605450:INFO: TIMERS PHASE: START: setInterval
1530377605450:INFO: TIMERS PHASE: END: setInterval
1530377605452:INFO: TIMERS PHASE: setInterval.setTimeout
1530377605452:INFO: TIMERS PHASE: START: setInterval
1530377605452:INFO: TIMERS PHASE: END: setInterval
1530377605454:INFO: TIMERS PHASE: setInterval.setTimeout
1530377605454:INFO: TIMERS PHASE: START: setInterval
1530377605454:INFO: TIMERS PHASE: END: setInterval
1530377605455:INFO: TIMERS PHASE: setInterval.setTimeout
1530377605455:INFO: TIMERS PHASE: START: setInterval
1530377605455:INFO: TIMERS PHASE: Max interval count exceeded. Goodbye.
1530377605456:INFO: TIMERS PHASE: END: setInterval
$

Cho xem nhiều hơn

Điều thực sự xảy ra ở đây là bạn đã thiết lập một bộ đếm thời gian chạy nhanh nhất có thể (với thời gian chờ 1ms). Khi cuộc gọi lại của nó chạy, nó sẽ tạo một bộ đếm thời gian một lần khác hết hạn sau 1 mili giây sau khi được tạo.

Bạn có phần đầu của bản đồ vòng lặp sự kiện. Để xây dựng trên đó, hãy thêm một số I/O, có nghĩa là gọi lại giai đoạn poll callback !

Poll phase callbacks

Tất cả các cuộc gọi lại yêu cầu I/O không chặn chạy trong giai đoạn thăm dò ý kiến. Dưới đây là một fs.readdir() ví dụ về gọi lại giai đoạn poll callback

Ví dụ 3. fs.readdir() ví dụ hiển thị poll callback

 01 const fs = require('fs');
 02 const logger = require('../common/logger');
 03 const ITERATIONS_MAX = 3;
 04 let iteration = 0;
 05 logger.info('START', 'MAINLINE');
 06 const timeout = setInterval(() => {
 07     logger.info('START: setInterval', 'TIMERS PHASE');
 08     if (iteration < ITERATIONS_MAX) {
 09         setTimeout(() => {
 10             logger.info('TIMERS PHASE', 'setInterval.setTimeout');
 11         });
 12         fs.readdir('../data', (err, files) => {
 13             if (err) throw err;
 14             logger.info('fs.readdir() callback: Directory contains: ' + files.length + ' files', 'POLL PHASE');
 15         });
 16     } else {
 17         logger.info('Max interval count exceeded. Goodbye.', 'TIMERS PHASE');
 18         clearInterval(timeout);
 19     }
 20     iteration++;
 21     logger.info('END: setInterval', 'TIMERS PHASE');
 22 }, 0);
 23 logger.info('END', 'MAINLINE');

Cuộc gọi I/O không chặn fs.readdir()bắt đầu trên dòng 12 và callback của nó thực thi trong poll ​​sau khi thao tác I/O hoàn tất.

output example3.js trông như thế này:

$ node example3
1530377719473:INFO: MAINLINE: START
1530377719475:INFO: MAINLINE: END
1530377719477:INFO: TIMERS PHASE: START: setInterval
1530377719477:INFO: TIMERS PHASE: END: setInterval
1530377719477:INFO: POLL PHASE: fs.readdir() callback: Directory contains: 7 files
1530377719477:INFO: setInterval.setTimeout: TIMERS PHASE
1530377719478:INFO: TIMERS PHASE: START: setInterval
1530377719478:INFO: TIMERS PHASE: END: setInterval
1530377719478:INFO: POLL PHASE: fs.readdir() callback: Directory contains: 7 files
1530377719479:INFO: setInterval.setTimeout: TIMERS PHASE
1530377719479:INFO: TIMERS PHASE: START: setInterval
1530377719479:INFO: TIMERS PHASE: END: setInterval
1530377719480:INFO: POLL PHASE: fs.readdir() callback: Directory contains: 7 files
1530377719481:INFO: setInterval.setTimeout: TIMERS PHASE
1530377719481:INFO: TIMERS PHASE: START: setInterval
1530377719481:INFO: TIMERS PHASE: Max interval count exceeded. Goodbye.
1530377719482:INFO: TIMERS PHASE: END: setInterval
$

Để giới hạn số lượng đầu ra, tôi đã giảm số lần lặp lại của lệnh setInterval()gọi lại bộ đếm thời gian xuống còn ba (xem Ví dụ 3, dòng 3).

Nếu có bất kỳ vi nhiệm vụ nào được lên lịch trong khi gọi lại giai đoạn thăm dò ý kiến, chúng sẽ được thực thi ngay lập tức trên OS (thay vì chờ giai đoạn thăm dò ý kiến ​​hoàn tất trước khi chạy vi nhiệm vụ). Bạn sẽ thấy nhiều hơn về điều này trong phần gọi lại Microtask sau trong hướng dẫn này.

Khi hàng đợi gọi lại giai đoạn thăm dò trống, giai đoạn thăm dò sẽ không hoạt động.

Nếu không có bộ hẹn giờ nào được lên lịch, thì giai đoạn thăm dò ý kiến ​​sẽ chờ bất kỳ yêu cầu I/O đang chờ xử lý nào hoàn tất, sau đó thực hiện lệnh gọi lại của chúng ngay lập tức.

Nếu  bất kỳ bộ hẹn giờ nào được lên lịch, vòng lặp sự kiện sẽ chuyển sang giai đoạn kiểm tra.

Kiểm tra callback

Callback cho Immediate được chạy trong giai đoạn kiểm tra. Ví dụ sau đây cho thấy một check phase callback ; 

Ví dụ 4. setImmediate()ví dụ hiển thị cuộc gọi lại giai đoạn kiểm tra của nó

 01 const fs = require('fs');
 02 const logger = require('../common/logger');
 03 const ITERATIONS_MAX = 3;
 04 let iteration = 0;
 05 logger.info('START', 'MAINLINE');
 06 const timeout = setInterval(() => {
 07     logger.info('START: setInterval', 'TIMERS PHASE');
 08     if (iteration < ITERATIONS_MAX) {
 09         setTimeout(() => {
 10             logger.info('setInterval.setTimeout', 'TIMERS PHASE');
 11         });
 12         fs.readdir('../data', (err, files) => {
 13             if (err) throw err;
 14             logger.info('fs.readdir() callback: Directory contains: ' + files.length + ' files', 'POLL PHASE');
 15         });
 16         setImmediate(() => {
 17             logger.info('setInterval.setImmediate', 'CHECK PHASE');
 18         });
 19     } else {
 20         logger.info('Max interval count exceeded. Goodbye.', 'TIMERS PHASE');
 21         clearInterval(timeout);
 22     }
 23     iteration++;
 24     logger.info('END: setInterval', 'TIMERS PHASE');
 25 }, 0);
 26 logger.info('END', 'MAINLINE');

Bộ Immediate timer được tạo trên dòng 16 trong giai đoạn bộ hẹn giờ (vì dòng 16 thực thi như một phần của setInterval()cuộc gọi lại), nhưng cuộc gọi lại của nó thực thi trong giai đoạn kiểm tra .

output example4.js trông như thế này:

$ node example4
1530377921982:INFO: MAINLINE: START
1530377921985:INFO: MAINLINE: END
1530377921986:INFO: TIMERS PHASE: START: setInterval
1530377921986:INFO: TIMERS PHASE: END: setInterval
1530377921987:INFO: POLL PHASE: fs.readdir() callback: Directory contains: 7 files
1530377921987:INFO: CHECK PHASE: setInterval.setImmediate
1530377921989:INFO: TIMERS PHASE: setInterval.setTimeout
1530377921989:INFO: TIMERS PHASE: START: setInterval
1530377921989:INFO: TIMERS PHASE: END: setInterval
1530377921989:INFO: CHECK PHASE: setInterval.setImmediate
1530377921989:INFO: POLL PHASE: fs.readdir() callback: Directory contains: 7 files
1530377921990:INFO: TIMERS PHASE: setInterval.setTimeout
1530377921991:INFO: TIMERS PHASE: START: setInterval
1530377921991:INFO: TIMERS PHASE: END: setInterval
1530377921991:INFO: CHECK PHASE: setInterval.setImmediate
1530377921991:INFO: POLL PHASE: fs.readdir() callback: Directory contains: 7 files
1530377921992:INFO: TIMERS PHASE: setInterval.setTimeout
1530377921992:INFO: TIMERS PHASE: START: setInterval
1530377921992:INFO: TIMERS PHASE: Max interval count exceeded. Goodbye.
1530377921993:INFO: TIMERS PHASE: END: setInterval
$

Để giới hạn số lượng output, tôi đã giảm số lần lặp lại của lệnh setInterval()gọi lại bộ đếm thời gian xuống còn ba (xem Ví dụ 4, dòng 3).

Callback microtask

Nhiệm vụ vi mô (microtask) là các callback được cung cấp cho process.nextTick()hoặc Promise.resolve().then()được thực hiện trong mainline hoặc một giai đoạn của vòng lặp sự kiện. Khi chúng được lên lịch, chúng sẽ đi vào hàng đợi gọi lại vi tác vụ được thực thi sau khi giai đoạn đó hoàn thành.

Dưới đây là ví dụ về process.nextTick()và Promise.resolve().then():

process.nextTick()

Khi vạch ra vòng lặp sự kiện, bạn có thể sử dụng microtask như một loại “dấu phân cách “. Ví dụ sau đây process.nextTick()cho thấy cách gọi lại vi tác vụ hoạt động

Ví dụ5. process.nextTick() ví dụ cho thấy cách gọi lại microtask hoạt động như các phase delimiters

01 const fs = require('fs');
02 const logger = require('../common/logger');
03 const ITERATIONS_MAX = 2;
04 let iteration = 0;
05 process.nextTick(() => {
06     logger.info('process.nextTick', 'MAINLINE MICROTASK');
07 });
08 logger.info('START', 'MAINLINE');
09 const timeout = setInterval(() => {
10     logger.info('START iteration ' + iteration + ': setInterval', 'TIMERS PHASE');
11
12     if (iteration < ITERATIONS_MAX) {
13         setTimeout((iteration) => {
14             logger.info('TIMER EXPIRED (from iteration ' + iteration + '): setInterval.setTimeout', 'TIMERS PHASE');
15             process.nextTick(() => {
16                 logger.info('setInterval.setTimeout.process.nextTick', 'TIMERS PHASE MICROTASK');
17             });
18         }, 0, iteration);
19         fs.readdir('../data', (err, files) => {
20             logger.info('fs.readdir() callback: Directory contains: ' + files.length + ' files', 'POLL PHASE');
21             process.nextTick(() => {
22                 logger.info('setInterval.fs.readdir.process.nextTick', 'POLL PHASE MICROTASK');
23             });
24         });
25         setImmediate(() => {
26             logger.info('setInterval.setImmediate', 'CHECK PHASE');
27             process.nextTick(() => {
28                 logger.info('setInterval.setTimeout.process.nextTick', 'CHECK PHASE MICROTASK');
29             });
30         });
31     } else {
32         logger.info('Max interval count exceeded. Goodbye.', 'TIMERS PHASE');
33         clearInterval(timeout);
34     }
35     logger.info('END iteration ' + iteration + ': setInterval', 'TIMERS PHASE');
36     iteration++;
37 }, 0);
38 logger.info('MAINLINE: END');

Trong mỗi callback, có một process.nextTick() đánh dấu sự kết thúc của giai đoạn mà nó đang chạy.

Ví dụ: setTimeout()cuộc gọi lại bắt đầu trên dòng 14 chạy trong giai đoạn hẹn giờ và lên lịch cho một vi tác vụ trên dòng 16, bao gồm một logger() .

Tương tự, cũng có process.nextTick()các dấu phân cách cho tất cả các giai đoạn khác (bao gồm cả mainline).

Ví dụ 5 trông như thế này:

1530401857782:INFO: MAINLINE: START
1530401857784:INFO: MAINLINE: END
1530401857785:INFO: MAINLINE MICROTASK: process.nextTick
1530401857786:INFO: TIMERS PHASE: START iteration 0: setInterval
1530401857786:INFO: TIMERS PHASE: END iteration 0: setInterval
1530401857787:INFO: POLL PHASE: fs.readdir() callback: Directory contains: 8 files
1530401857787:INFO: POLL PHASE MICROTASK: setInterval.fs.readdir.process.nextTick
1530401857787:INFO: CHECK PHASE: setInterval.setImmediate
1530401857787:INFO: CHECK PHASE MICROTASK: setInterval.setTimeout.process.nextTick
1530401857787:INFO: TIMERS PHASE: TIMER EXPIRED (from iteration 0): setInterval.setTimeout
1530401857787:INFO: TIMERS PHASE: START iteration 1: setInterval
1530401857788:INFO: TIMERS PHASE: END iteration 1: setInterval
1530401857788:INFO: TIMERS PHASE MICROTASK: setInterval.setTimeout.process.nextTick
1530401857788:INFO: POLL PHASE: fs.readdir() callback: Directory contains: 8 files
1530401857788:INFO: POLL PHASE MICROTASK: setInterval.fs.readdir.process.nextTick
1530401857788:INFO: CHECK PHASE: setInterval.setImmediate
1530401857788:INFO: CHECK PHASE MICROTASK: setInterval.setTimeout.process.nextTick
1530401857788:INFO: TIMERS PHASE: TIMER EXPIRED (from iteration 1): setInterval.setTimeout
1530401857788:INFO: TIMERS PHASE: START iteration 2: setInterval
1530401857788:INFO: TIMERS PHASE: Max interval count exceeded. Goodbye.
1530401857788:INFO: TIMERS PHASE: END iteration 2: setInterval
1530401857788:INFO: TIMERS PHASE MICROTASK: setInterval.setTimeout.process.nextTick

Đầu tiên, dòng chính chạy, tiếp theo là nhiệm vụ nhỏ của nó. Sau đó là giai đoạn hẹn giờ, giai đoạn thăm dò ý kiến, giai đoạn kiểm tra, v.v. 

Sau mỗi giai đoạn vòng lặp sự kiện, có một process.nextTick()cuộc gọi lại dấu phân cách để đánh dấu sự kết thúc của giai đoạn đó. Vì bạn biết lệnh gọi lại dấu phân cách pha PHẢI chạy sau pha đó, bất kỳ đầu ra nào tiếp theo PHẢI đến từ pha tiếp theo của vòng lặp sự kiện.

Promise.resolve().then()

Callback của Promise() đã giải quyết then()được thực thi dưới dạng một vi nhiệm vụ giống như process.nextTick(). Mặc dù, nếu cả hai đều nằm trong cùng một hàng đợi vi tác vụ, lệnh gọi lại for process.nextTick()sẽ được thực hiện trước.

Ví dụ: nếu một promise được giải quyết trong giai đoạn hẹn giờ và có then()chức năng xử lý bị ràng buộc, nó sẽ chạy ngay sau giai đoạn hẹn giờ trước khi vòng lặp sự kiện tiếp tục.

Xem xét Ví dụ 6, đây là một Promise.resolve().then()ví dụ cho thấy cách gọi lại vi tác vụ hoạt động như các dấu phân cách pha

Ví dụ 6. Promise.resolve().then()ví dụ cho thấy cách gọi lại vi tác vụ hoạt động như các dấu phân cách pha

01 const fs = require('fs');
02 const logger = require('../common/logger');
03 const ITERATIONS_MAX = 2;
04 let iteration = 0;
05 Promise.resolve().then(() => {
06     // Microtask callback runs AFTER mainline, even though the code is here
07     logger.info('Promise.resolve.then', 'MAINLINE MICROTASK');
08 });
09 logger.info('START', 'MAINLINE');
10 const timeout = setInterval(() => {
11     logger.info('START iteration ' + iteration + ': setInterval', 'TIMERS PHASE');
12     if (iteration < ITERATIONS_MAX) {
13         setTimeout((iteration) => {
14             logger.info('TIMER EXPIRED (from iteration ' + iteration + '): setInterval.setTimeout', 'TIMERS PHASE');
15             Promise.resolve().then(() => {
16                 logger.info('setInterval.setTimeout.Promise.resolve.then', 'TIMERS PHASE MICROTASK');
17             });
18         }, 0, iteration);
19         fs.readdir('../data', (err, files) => {
20             if (err) throw err;
21             logger.info('fs.readdir() callback: Directory contains: ' + files.length + ' files', 'POLL PHASE');
22             Promise.resolve().then(() => {
23                 logger.info('setInterval.fs.readdir.Promise.resolve.then', 'POLL PHASE MICROTASK');
24             });
25         });
26         setImmediate(() => {
27             logger.info('setInterval.setImmediate', 'CHECK PHASE');
28             Promise.resolve().then(() => {
29                 logger.info('setInterval.setTimeout.Promise.resolve.then', 'CHECK PHASE MICROTASK');
30             });
31         });
32     } else {
33         logger.info('Max interval count exceeded. Goodbye.', 'TIMERS PHASE');
34         clearInterval(timeout);
35     }
36     logger.info('END iteration ' + iteration + ': setInterval', 'TIMERS PHASE');
37     iteration++;
38 }, 0);
39 logger.info('END', 'MAINLINE');

Ví dụ 6 giống như Ví dụ 5 ngoại trừ việc nó sử dụng các trình xử lý promise đã giải quyết làm các dấu phân cách pha. Đầu ra trông như thế này:

$ node example6
1530402106144:INFO: MAINLINE: START
1530402106146:INFO: MAINLINE: END
1530402106147:INFO: MAINLINE MICROTASK: Promise.resolve.then
1530402106148:INFO: TIMERS PHASE: START iteration 0: setInterval
1530402106148:INFO: TIMERS PHASE: END iteration 0: setInterval
1530402106149:INFO: POLL PHASE: fs.readdir() callback: Directory contains: 8 files
1530402106149:INFO: POLL PHASE MICROTASK: setInterval.fs.readdir.Promise.resolve.then
1530402106149:INFO: CHECK PHASE: setInterval.setImmediate
1530402106149:INFO: CHECK PHASE MICROTASK: setInterval.setTimeout.Promise.resolve.then
1530402106151:INFO: TIMERS PHASE: TIMER EXPIRED (from iteration 0): setInterval.setTimeout
1530402106151:INFO: TIMERS PHASE: START iteration 1: setInterval
1530402106151:INFO: TIMERS PHASE: END iteration 1: setInterval
1530402106151:INFO: TIMERS PHASE MICROTASK: setInterval.setTimeout.Promise.resolve.then
1530402106151:INFO: POLL PHASE: fs.readdir() callback: Directory contains: 8 files
1530402106151:INFO: POLL PHASE MICROTASK: setInterval.fs.readdir.Promise.resolve.then
1530402106151:INFO: CHECK PHASE: setInterval.setImmediate
1530402106151:INFO: CHECK PHASE MICROTASK: setInterval.setTimeout.Promise.resolve.then
1530402106153:INFO: TIMERS PHASE: TIMER EXPIRED (from iteration 1): setInterval.setTimeout
1530402106153:INFO: TIMERS PHASE: START iteration 2: setInterval
1530402106153:INFO: TIMERS PHASE: Max interval count exceeded. Goodbye.
1530402106153:INFO: TIMERS PHASE: END iteration 2: setInterval
1530402106153:INFO: TIMERS PHASE MICROTASK: setInterval.setTimeout.Promise.resolve.then
$

Bạn có thể thấy rằng cả hai loại microtask đều chạy ngay sau giai đoạn mà chúng đã được lên lịch.

Events

Một sự kiện là một cái gì đó có ý nghĩa xảy ra trong một ứng dụng. Thời gian chạy hướng sự kiện như Node phát ra các sự kiện ở một số nơi nhất định và phản hồi chúng ở những nơi khác.

Thật dễ dàng để tạo sự kiện tùy chỉnh của riêng bạn, như Ví dụ 7 :

Ví dụ 7. Sử dụng custom event: simpleEvent

01   // The Node EventEmitter
02   const EventEmitter = require('events');
03   // Create an instance of EventEmitter
04   const eventEmitter = new EventEmitter();
05
06   // The common logger
07   const logger = require('../common/logger');
08
09   logger.info('START', 'MAINLINE');
10
11   logger.info('Registering simpleEvent handler', 'MAINLINE');
12   eventEmitter.on('simpleEvent', (eventName, message, source, timestamp) => {
13       logger.info('Received event: ' + timestamp + ': ' + source + ':[' + eventName + ']: ' + message, 'EventEmitter.on()');
14   });
15
16   // Get the current time
17   let hrtime = process.hrtime();
18   eventEmitter.emit('simpleEvent', 'simpleEvent', 'Custom event says what?', 'MAINLINE', (hrtime[0] * 1e9 + hrtime[1] ) / 1e6);
19
20   logger.info('END', 'MAINLINE');

Đầu tiên, bạn cần một tham chiếu đến events mô-đun (dòng 2) và một thể hiện của EventEmitter đối tượng (dòng 4).

Sau đó, bạn đăng ký một trình lắng nghe sự kiện Function bằng EventEmitter.on() (dòng 12). Khi simpleEvent được phát ra, chức năng nghe sẽ được gọi và ghi lại một thông báo (dòng 13).

Khi bạn chạy nó, đầu ra trông như thế này:

$ node example7
1530379926998:INFO: MAINLINE: START
1530379927000:INFO: MAINLINE: Registering simpleEvent handler
1530379927000:INFO: EventEmitter.on(): Received event: 553491474.966337: MAINLINE:[simpleEvent]: Custom event says what?
1530379927000:INFO: MAINLINE: END
$

Tuy nhiên, ta cần dọn dẹp những thứ sau:

  • Chuỗi ký tự lặp lại: di chuyển chúng vào một mô-đun chung
  • Chức năng dấu thời gian: chuyển chức năng này vào simple-utils vì đây giống như thứ bạn có thể muốn sử dụng lại
  • Các thuộc tính sự kiện ( eventNamemessage, v.v.) có thể được di chuyển vào một mô-đun chung và được sử dụng lại như một đối tượng
  • Thay thế bộ ghi bảng điều khiển ( logger) bằng bộ ghi dựa trên sự kiện có cùng giao diện để tương thích ngược
  • Tạo một biểu thức hàm không thể hủy ngay lập tức (IIFE) được gọi mainline có chứa mã dòng chính để không nghi ngờ gì về vị trí của dòng chính

Mã tái cấu trúc trông như thế này:

Ví dụ 8. Mã ví dụ 7 được cấu trúc lại

01 const EventEmitter = require('events');
02 const eventEmitter = new EventEmitter();
03
04 // EventInfo module
05 const { EventInfo } = require('../common/event-info');
06 // Event Logger module
07 const logger = require('../common/event-logger');
08 // Simple utilities module
09 const simpleUtils = require('../common/simple-utils');
10
11 // Constants (from constants module)
12 const { MAINLINE, START, END } = require('../common/constants');
13
14 // The custom event name
15 const EVENT_NAME = 'simpleEvent';
16
17 /**
18  * The mainline function.
19  */
20 (function mainline() {
21
22     logger.info(START, MAINLINE);
23
24     logger.info('Registering ' + EVENT_NAME + ' handler', MAINLINE);
25     eventEmitter.on(EVENT_NAME, (eventInfo) => {
26         logger.info('Received event: ' + eventInfo.toString(), 'EventEmitter.on()');
27     });
28     // Emit the event
29     eventEmitter.emit(EVENT_NAME, new EventInfo(EVENT_NAME, 'Custom event says what?', MAINLINE, simpleUtils.hrtimeToMillis(process.hrtime())));
30
31     logger.info(END, MAINLINE);
32
33 })();

Đầu ra trông như thế này:

$ node example8
553643510.792406: MAINLINE:[INFO]: START
553643510.975569: MAINLINE:[INFO]: Registering simpleEvent handler
553643511.05733: EventEmitter.on():[INFO]: Received event: 553643510.994694: MAINLINE:[simpleEvent]: Custom event says what?
553643511.060924: MAINLINE:[INFO]: END

Đầu ra rất giống với đầu ra của ví dụ trước, ngoại trừ dấu thời gian, hiện là giá trị mili giây từ process.hrtime(), thay vì millis

Tôi đã giải quyết tất cả các mối quan tâm của mình:

  • Chuỗi ký tự lặp lại (dòng 12, 22, 24, 29, 31): di chuyển vào một mô-đun chung
  • Chức năng dấu thời gian (dòng 9, 29): di chuyển vào simple-utils(cái này giống như thứ bạn có thể muốn sử dụng lại)
  • Các thuộc tính sự kiện ( eventNamemessage, v.v.) có thể được di chuyển vào một mô-đun chung và được sử dụng lại làm EventInfođối tượng – dòng 5, 29
  • Thay thế bộ ghi bảng điều khiển ( logger) bằng bộ ghi dựa trên sự kiện có cùng giao diện – dòng 7, 22, 24, 26, 31
  • Tạo một biểu thức hàm có thể sử dụng ngay lập tức (IIFE) được gọi mainlinecó chứa mã dòng chính – dòng 20 – 33

Bạn đã biết về cách thức hoạt động của các sự kiện Node. Đã đến lúc xem các sự kiện hoạt động như thế nào với Streams API.

Streams

Streams là một Trình phát sự kiện được sử dụng để xử lý những thứ “có thể phát trực tuyến”. Điều này bao gồm các tệp và kết nối HTTP chẳng hạn.

Bây giờ hãy xem Ví dụ 9, bắt đầu với mainline():

Ví dụ 9. example9.js

01 /**
02  * The mainline function - IIFE
03  */
04 (function mainline() {
05     // Create the ReadableStream
06     const readableStream = fs.createReadStream(appSettings.inputFileName, 'utf8');
07     // Register event listeners for the input file
08     registerReadableStreamEventListeners(readableStream);
09     // The output file (WritableStream)
10     const writableStream = fs.createWriteStream(appSettings.outputFileName, 'utf8');
11     // Register event listeners for the output file
12     registerWritableStreamEventListeners(writableStream, readableStream);
13 })();

Khi bạn chạy node example9.jsmainline()hàm này được thực thi dưới dạng một biểu thức hàm có thể sử dụng ngay lập tức (IIFE), như bạn đã thấy trong Ví dụ 8. Tôi sử dụng mẫu đó cho tất cả các tập lệnh đầu vào từ thời điểm này trở đi trong khóa học.

Nó làm 4 điều:

  1. Dòng 6: Tạo một ReadableStream bằng cách sử dụng các cài đặt đã chỉ định (từ một mô-đun mới có tên appSettings, cũng là một mẫu tôi làm theo từ giờ trở đi).
  2. Dòng 8: Đăng ký trình xử lý sự kiện cho ReadableStream.
  3. Dòng 10: Tạo một WritableStream bằng cách sử dụng các cài đặt đã chỉ định từ appSettings mô-đun.
  4. Dòng 12: Đăng ký trình xử lý sự kiện cho WritableStream.

Hãy xem mã đăng ký trình xử lý sự kiện cho ReadableStream:

Ví dụ 9. Hàm registerReadableStreamEventListeners() trong example9.js

01 function registerReadableStreamEventListeners(readableStream) {
02     readableStream.on('open', (fd) => {
03         logger.info('open event received, file descriptor: ' + fd, 'ReadableStream.on(open).callback');
04     });
05     readableStream.on('data', (chunk) => {
06         logger.info('data event received: chunk size: ' + chunk.length, 'ReadableStream.on(data).callback');
07     });
08     readableStream.on('error', (err) => {
09         logger.info('Something has gone horribly wrong: ' + err.message, 'ReadableStream.on(error).callback');
10     });
11     readableStream.on('close', () => {
12         logger.info('close event received', 'ReadableStream.on(close).callback');
13     });
14 }

Mã này đăng ký 4 cuộc gọi lại trình lắng nghe sự kiện:

  • Dòng 2: open sự kiện, được phát ra khi tệp sao lưu luồng được mở. Nó sử dụng fd, là bộ mô tả tệp số nguyên.
  • Dòng 5: sự kiện data] , được phát ra bất cứ khi nào một “khối” dữ liệu (64kb trên máy Mac của tôi, ymmv) được đọc. nó sử dụng chunk, biểu thị đoạn dữ liệu đã được đọc.
  • Dòng 8: sự kiện ‘lỗi’] , được phát ra khi xảy ra lỗi (bạn phải luôn cung cấp trình xử lý lỗi trừ khi bạn có lý do thực sự chính đáng để không cung cấp). Nó sử dụng err– đối tượng Error] .
  • Dòng 11: sự kiện Error] , được phát ra khi luồng đã bị đóng. Nó không sử dụng đối số.

Bây giờ, hãy xem các trình lắng nghe sự kiện cho WritableStream:

Ví dụ 9. registerWritableStreamEventListeners()

01 function registerWritableStreamEventListeners(writableStream, readableStream) {
02     writableStream.on('open', (fd) => {
03         logger.info('open event received, file descriptor: ' + fd, 'WritableStream.on(open).callback');
04         // Connect the readableStream to the writableStream
05         readableStream.pipe(writableStream);
06     });
07     writableStream.on('pipe', (src) => {
08         logger.info('pipe event received, let the data flow!', 'WritableStream.on(pipe).callback');
09     });
10     writableStream.on('error', (err) => {
11         logger.info('Something has gone horribly wrong: ' + err.message, 'WritableStream.on(error).callback');
12     });
13     writableStream.on('close', () => {
14         logger.info('close event received', 'WritableStream.on(close).callback');
15     });
16 }

Chức năng này registerWritableStreamEventListeners()thực hiện khá nhiều những gì đối tác của nó làm, với hai điểm khác biệt. 

Trên dòng 5, trong open trình xử lý sự kiện, mã này kết nối ReadbleStreamtrực tiếp với hàm WritableStreamthông qua ReadableStream.pipe(), hàm này tạo bản sao của tệp đầu vào. Sự khác biệt thứ hai là mã này đăng ký một trình xử lý cho pipe sự kiện và ghi lại một thông báo cho biết sự kiện đã được nhận.

Tôi sẽ để phần mã còn lại để bạn nghiên cứu như một bài tập. Phần bổ sung duy nhất cho các mô-đun hỗ trợ, mà bạn đã thấy trong Ví dụ 8, là việc sử dụng mô-đun cài đặt ứng dụng được gọi là mô-đun cung cấp appSettingsmột cách để tập trung các cài đặt đó, thay vì mã hóa cứng chúng trong tập lệnh đầu vào Nút như tôi đã từng làm cho đến thời điểm này.

khi bạn chạy example9.js, kết quả sẽ như thế này:

$ node example9
527160650.291643: ReadableStream.on(open).callback:[INFO]: open event received, file descriptor: 13
527160650.956652: WritableStream.on(open).callback:[INFO]: open event received, file descriptor: 14
527160651.230392: WritableStream.on(pipe).callback:[INFO]: pipe event received, let the data flow!
527160651.686702: ReadableStream.on(data).callback:[INFO]: data event received: chunk size: 65536
527160653.008806: ReadableStream.on(data).callback:[INFO]: data event received: chunk size: 3009
527160654.401119: ReadableStream.on(close).callback:[INFO]: close event received
527160654.459593: WritableStream.on(close).callback:[INFO]: close event received

Điều đó bao gồm phần giới thiệu về Stream. Tôi đã đặt liên kết đến các tài nguyên trong suốt hướng dẫn này. Tôi khuyến khích bạn theo dõi chúng đến tài liệu Node để hiểu sâu hơn về các khái niệm.

Event loop và Stream

Như bạn đã thấy trong Ví dụ 5 và 6, bạn có thể khai thác phần lớn hệ thống ống nước của Node để xem và hiểu vòng lặp sự kiện đang làm gì.

Bây giờ, hãy làm điều đó bằng cách sử dụng API Stream. Ví dụ 10 sử dụng phiên bản sửa đổi của Ví dụ 9 (không có WritableStreamnội dung) để vạch ra vòng lặp sự kiện. Khi bạn chạy, example10.js đầu ra trông như thế này:

Ví dụ 10. Sử dụng StreamAPI để vạch ra vòng lặp sự kiện khi nó đọc luồng đầu vào

$ node example10
528214326.789891: setInterval:[DEBUG]: TIMERS PHASE: GREETINGS
528214327.081744: ReadableStream.on(open).callback(process.nextTick):[DEBUG]: TIMERS PHASE DONE
528214327.303483: ReadableStream.on(open).callback:[INFO]: open event received
528214331.93755: ReadableStream.on(open).callback(process.nextTick):[DEBUG]: POLL PHASE DONE
528214332.141504: ReadableStream.on(ready).callback(setImmediate):[DEBUG]: CHECK PHASE: GREETINGS
528214332.176539: ReadableStream.on(ready).callback(setImmediate.process.nextTick):[DEBUG]: CHECK PHASE DONE
528214332.205251: setInterval:[DEBUG]: TIMERS PHASE: GREETINGS
528214332.245918: ReadableStream.on(ready).callback(setTimeout):[DEBUG]: TIMER EXPIRED
528214332.261408: ReadableStream.on(open).callback(process.nextTick):[DEBUG]: TIMERS PHASE DONE
528214332.28407: ReadableStream.on(ready).callback(setTimeout.process.nextTick):[DEBUG]: TIMERS PHASE DONE
528214332.795419: ReadableStream.on(data).callback:[INFO]: data event received: chunk size: 65536
528214332.870552: ReadableStream.on(data).callback(process.nextTick):[DEBUG]: POLL PHASE DONE
528214333.019428: ReadableStream.on(data).callback(setImmediate):[DEBUG]: CHECK PHASE: GREETINGS
528214333.046891: ReadableStream.on(data).callback(setImmediate.process.nextTick):[DEBUG]: CHECK PHASE DONE
528214333.057797: setInterval:[DEBUG]: TIMERS PHASE: GREETINGS
528214333.089672: ReadableStream.on(data).callback(setTimeout):[DEBUG]: TIMER EXPIRED
528214333.101422: ReadableStream.on(open).callback(process.nextTick):[DEBUG]: TIMERS PHASE DONE
528214333.117819: ReadableStream.on(data).callback(setTimeout.process.nextTick):[DEBUG]: TIMERS PHASE DONE
528214333.161942: ReadableStream.on(data).callback:[INFO]: data event received: chunk size: 3009
528214333.181389: ReadableStream.on(data).callback(process.nextTick):[DEBUG]: POLL PHASE DONE
528214333.228088: ReadableStream.on(data).callback(setImmediate):[DEBUG]: CHECK PHASE: GREETINGS
528214333.235674: ReadableStream.on(data).callback(setImmediate.process.nextTick):[DEBUG]: CHECK PHASE DONE
528214333.770969: ReadableStream.on(close).callback:[INFO]: close event received
528214336.751145: ReadableStream.on(close).callback(process.nextTick):[DEBUG]: POLL PHASE DONE
528214336.790267: ReadableStream.on(close).callback(setImmediate):[DEBUG]: CHECK PHASE: GREETINGS
528214336.819611: ReadableStream.on(close).callback(setImmediate.process.nextTick):[DEBUG]: CHECK PHASE DONE
528214336.832176: ReadableStream.on(data).callback(setTimeout):[DEBUG]: TIMER EXPIRED
528214336.858322: ReadableStream.on(close).callback(setTimeout):[DEBUG]: TIMER EXPIRED
528214336.873914: ReadableStream.on(data).callback(setTimeout.process.nextTick):[DEBUG]: TIMERS PHASE DONE
528214336.889914: ReadableStream.on(close).callback(setTimeout.process.nextTick):[DEBUG]: TIMERS PHASE DONE

Bạn có thể phát hiện ra các giai đoạn và khi chúng chạy không? Làm thế nào về các dấu phân cách?

Tự mình chơi xung quanh example10.jsvà cảm nhận về cách hoạt động của vòng lặp sự kiện với Luồng. Sẽ có các câu hỏi trong bài kiểm tra liên quan đến Luồng và ánh xạ vòng lặp sự kiện.

Truyền đối số cho bộ hẹn giờ

Tôi sẽ kết thúc phần này của hướng dẫn bằng cách chỉ cho bạn cách truyền đối số cho các hàm hẹn giờ khác nhau, vì thỉnh thoảng bạn sẽ cần thực hiện việc này. Điều này sẽ có ích trong các hướng dẫn sau này.

Tôi đã tập hợp một ví dụ chỉ ra cách truyền đối số cho các hàm hẹn giờ sau (tôi process.nextTick()cũng đưa vào đó, mặc dù đó không phải là hàm hẹn giờ). Tệp nguồn là example11.js, bạn có thể tự kiểm tra. Nó hơi dài để bao gồm toàn bộ, vì vậy tôi sẽ trình bày từng phần một.

bộ đếm thời gian setInterval()

Đoạn mã sau chỉ cho bạn cách truyền đối số cho setInterval():

01 const timeout = setInterval((startTime) => {
02     logger.info('START iteration ' + iteration + ': setInterval', 'TIMERS PHASE');
03
04     // Run for about 10 ms
05     if (Date.now() < startTime + 10) {
06 .
07 .
08     } else {
09         logger.info('Max interval count exceeded. Goodbye.', 'TIMERS PHASE');
10         // Kill the interval timer
11         clearInterval(timeout);
12     }
13     logger.info('END iteration ' + iteration + ': setInterval', 'TIMERS PHASE');
14     iteration++;
15 }, 0, Date.now());

Bộ hẹn giờ khoảng thời gian được tạo trên dòng 1 và danh sách đối số của nó được cung cấp trên dòng 15, bao gồm: (1) ( delay0ms) và (2) thời gian hiện tại tính bằng mili giây (từ Date.now()). Thời gian hiện tại được chuyển đến cuộc gọi lại dưới dạng startTimetham số trên dòng 1, mà cuộc gọi lại sử dụng để giúp xác định thời điểm gọi clearInterval()(dòng 11).

bộ hẹn giờ setTimeout()

Đoạn mã sau chỉ cho bạn cách truyền đối số chosetTimeout()

01 const timeout = setInterval((startTime) => {
02     logger.info('START iteration ' + iteration + ': setInterval', 'TIMERS PHASE');
03
04     // Run for about 10 ms
05     if (Date.now() < startTime + 10) {
06         setTimeout((currentIteration) => {
07             logger.info('TIMER EXPIRED (from iteration ' + currentIteration + '): setInterval.setTimeout', 'TIMERS PHASE');
08             process.nextTick(() => {
09                 logger.info('setInterval.setTimeout.process.nextTick', 'TIMERS PHASE MICROTASK');
10             });
11         }, 0, iteration);
12 .
13 .
14     logger.info('END iteration ' + iteration + ': setInterval', 'TIMERS PHASE');
15
16     iteration++;
17 }, 0, Date.now());

setTimeout()được gọi ở dòng 6 và danh sách đối số của nó được cung cấp ở dòng 11, bao gồm (1) delay(0ms) và (2) giá trị lặp hiện tại. Vòng lặp hiện tại được chuyển khi bộ hẹn giờ được tạo, tạo ra một lần đóng, để khi đối số được chuyển khi setTimeout()lệnh gọi lại của ‘ được chạy, nó có thể báo cáo vòng lặp khoảng thời gian đã được thực hiện khi nó được lên lịch.

bộ đếm thời gian setImmediate()

Bạn cũng có thể chuyển nhiều đối số cho bất kỳ chức năng hẹn giờ nào trong số này, như tôi sẽ trình bày với setImmediate():

01 const timeout = setInterval((startTime) => {
02     logger.info('START iteration ' + iteration + ': setInterval', 'TIMERS PHASE');
03
04     // Run for about 10 ms
05     if (Date.now() < startTime + 10) {
06 .
07 .
08         setImmediate((startTime, currentIteration) => {
09             logger.info('Callback executed (scheduled during iteration ' + currentIteration + '): setInterval.setImmediate', 'CHECK PHASE');
10             logger.info('Elapsed time: ' + (Date.now() - startTime) + 'ms', 'CHECK PHASE');
11             process.nextTick((startTime) => {
12                 logger.info('setInterval.setTimeout.process.nextTick', 'CHECK PHASE MICROTASK');
13                 logger.info('Elapsed time: ' + (Date.now() - startTime) + 'ms', 'CHECK PHASE');
14             }, startTime);
15         }, startTime, iteration);
16     } else {
17 .
18 .
19     iteration++;
20 }, 0, Date.now());
21
22 logger.info('END', 'mainline');

Bộ đếm thời gian ngay lập tức được tạo trên dòng 8 và danh sách đối số của nó được chuyển vào dòng 15, bao gồm (1) startTimevà (2) iteration(nghĩa là lần lặp hiện tại). Một lần nữa, khi bộ đếm thời gian được tạo, các giá trị đối số trở thành một phần của bao đóng cho hàm gọi lại để khi nó được gọi, nó sẽ nhận các giá trị của biến khi bao đóng được tạo.

bộ đếm thời gian process.nextTick()

Bạn cũng có thể chuyển một số đối số tùy ý process.nextTick(). Tôi sẽ giới thiệu bạn đến danh sách trước đó. Trên dòng 11, process.nextTick()cuộc gọi được thực hiện và startTimegiá trị được truyền vào dòng 14. Khi cuộc gọi lại được gọi, thời gian bắt đầu được chuyển và cuộc gọi lại sử dụng giá trị đó để tạo thời gian đã trôi qua.

Phần kết luận

Trong hướng dẫn này, chúng ta đã xem xét kỹ hơn về vòng lặp sự kiện. Bạn đã học cách vạch ra các giai đoạn của nó bằng cách sử dụng lệnh gọi lại cho bộ hẹn giờ, I/O process.nextTick()và promise().

Tiếp theo, bạn đã tìm hiểu về các sự kiện Node và cách tạo custom event. Sau đó, bạn đã tìm hiểu về Stream và thậm chí sử dụng chúng để vạch ra các giai đoạn của vòng lặp sự kiện. Cuối cùng, bạn đã học cách truyền đối số cho timer callbacks.


Like it? Share with your friends!

1083
1.8k share, 1083 points

What's Your Reaction?

hate hate
0
hate
confused confused
0
confused
fail fail
0
fail
fun fun
0
fun
geeky geeky
0
geeky
love love
0
love
lol lol
2
lol
omg omg
0
omg
win win
0
win

0 Comments

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *