JavaScript >> Javascript 文檔 >  >> Node.js

如何在 Node.js 中使用定時器和事件

Node.js 中的事件和計時器

Node.js 有多個用於處理事件以及調度代碼執行的實用程序。這些實用程序相結合,使您能夠在正確的時間做出反應,例如:

  • 在用戶註銷時清除會話數據
  • 為從 API 調用接收結果安排超時,並指定在超時時運行的錯誤處理代碼
  • 在退出 Node.js 之前關閉數據庫連接

在本文中,我們將討論計時器在 Node.js 中的工作方式。我們還將介紹 Node.js 事件循環的工作原理以及如何利用 Node 的事件處理功能。

定時器

我們將看到的第一組實用程序是 setTimeout , setImmediate , 和 setInterval 計時實用程序。有了這些工具,我們就可以控制代碼執行的時機了。

為什麼這很重要?在 Node.js 中,類似於使用 C、Python、Java 等其他編程語言時,它有助於安排某些函數重複運行。

例如,假設我們要將某些文件從接收位置複製到永久存檔。這將是安排文件傳輸的好方案。每隔一段時間,我們可以檢查新文件,如果有的話,將它們複製到備份位置。

setTimeout

使用 setTimeout ,我們可以安排代碼在經過一定時間後執行。

// setTimeout.js

let cue = 'The actors are here!';

// However, the cue is not announced until at least 5000ms have
// passed through the use of setTimeout
setTimeout(function() {
    return console.log(cue);
}, 5000);

// This console log is executed right away
console.log('An exploration of art and music. And now, as we wait for the actors...');

要執行此代碼並查看其運行情況,請運行 node setTimeout.js 在你的終端:

$ node setTimeout.js
An exploration of art and music. And now, as we wait for the actors...
The actors are here!

請注意即使 console('An exploration...') 調用是之後 我們的 console.log(cue) 調用,還是先執行。

這裡要實現的技巧是,代碼只保證在至少經過一段時間後才能執行,而不是立即執行。

setInterval

在需要重複、定期執行代碼的情況下,例如長輪詢,然後 setInterval 方法將比 setTimeout 更自然 .使用此函數,我們可以指定每 X 秒執行一次的函數。該函數實際上以毫秒為單位接受其參數,因此您必須在輸入參數之前自己進行轉換。

假設我們想檢查麥當勞免下車的隊列長度,以便我們程序的用戶可以在最佳時間衝出。使用 setInterval ,我們可以反複檢查隊列的長度,並在海岸暢通時告訴他們。

// setInterval.js

// This function simulates us checking the length
// of a McDonald's drive-through queue
let getQueueLength = function() {
    return Math.round(12 * Math.random());
};

// We would like to retrieve the queue length at regular intervals
// this way, we can decide when to make a quick dash over
// at the optimal time
setInterval(function() {
    let queueLength = getQueueLength();

    console.log(`The queue at the McDonald's drive-through is now ${queueLength} cars long.`);

    if (queueLength === 0) {
        console.log('Quick, grab your coat!');
    }

    if (queueLength > 8) {
        return console.log('This is beginning to look impossible!');
    }
}, 3000);

你可以看到下面的輸出。使用 node setInterval.js 運行代碼 ,如下圖。

$ node setTimeout.js 
The queue at the McDonald's drive-through is now 6 cars long.
The queue at the McDonald's drive-through is now 0 cars long.
Quick, grab your coat!
The queue at the McDonald's drive-through is now 1 cars long.
The queue at the McDonald's drive-through is now 3 cars long.
The queue at the McDonald's drive-through is now 9 cars long.
This is beginning to look impossible!
The queue at the McDonald's drive-through is now 0 cars long.
Quick, grab your coat!
The queue at the McDonald's drive-through is now 10 cars long.
This is beginning to look impossible!

setImmediate

如果我們希望函數盡快執行,我們使用 setImmediate .我們這樣執行的函數會在所有setTimeout之前執行 或 setInterval 當前 Node.js 事件循環完成調用事件回調後立即調用。

這是一個正在處理的例子。您可以使用命令 node setImmediate.js 運行此代碼

// setImmediate.js

// A timeout
setTimeout(function() {
    console.log('I am a timeout');
}, 5000);

// An interval
setInterval(function() {
    console.log('I am an interval');
}, 5000);

// An immediate, its callback will be executed before those defined above
setImmediate(function() {
    console.log('I am an immediate');
});

// IO callbacks and code in the normal event loop runs before the timers
console.log('I am a normal statement in the event loop, guess what comes next?');
$ node setImmediate.js 
I am a normal statement in the event loop, guess what comes next?
I am an immediate
I am a timeout
I am an interval
I am an interval
I am an interval
...

setImmediate 回調,雖然在之後定義 setInterval 的那些 和 setTimeout ,會跑在他們前面。

事件循環

您可能想到的一個問題是“Node.js 如何跟踪所有這些時間、計時器和事件?執行順序如何確定優先級?”這是一個很好的查詢線,需要查看被稱為“Node.js 事件循環”的東西。

那麼,什麼是事件循環呢?

事件循環只是 Node.js 通過計算處理切換的重複循環。由於它不能同時進行所有可能的計算,因為它是單線程的,所以它在一個被稱為事件循環的定義良好的循環中從計算切換到計算。

事件循環有以下幾個基本階段:

  • 計時器 - 執行已使用 setTimeout 安排的回調 和 setInterval
  • 掛起的回調 - 執行任何準備好運行的回調
  • 空閒,準備 - Node.js 內部
  • 輪詢 - 接受傳入連接和數據處理
  • 檢查 - 調用使用 setImmediate 設置的回調
  • 關閉回調 - 為關閉事件運行回調

事件循環是在 Node.js 中處理事件和其他異步回調的支柱。它允許我們在循環過程中會被擊中的某些點上打鉤。

用回調響應異步返回

鑑於 Node.js 的單線程特性,文件讀取或數據庫查詢等長時間運行的操作會快速卸載到操作系統,然後 Node.js 會照常繼續其事件循環。這使事情變得高效和快速。

免費電子書:Git Essentials

查看我們的 Git 學習實踐指南,其中包含最佳實踐、行業認可的標準以及隨附的備忘單。停止谷歌搜索 Git 命令並真正學習 它!

這些操作系統進程如何與 Node.js 進程交互?通過回調的方式。我們使用回調在後台異步處理事物,然後在異步作業完成後掛鉤回事件循環。要在其他編程語言中獲得這種功能,您可以使用任務隊列,例如 Python 中的 Celery 或 Ruby 中的 Sidekiq。在 Node.js 中,因為 Event Loop 和 Node.js 的異步執行已經自動為你排隊,你可以免費獲得這個異步處理。

要查看實際的回調,我們將從文件系統中讀取一個文件並使用回調打印出內容。

第一步是創建文件。在這種情況下,我們使用的是一個文本文件,其中包含 T.S.艾略特。您可以替換自己的文件。此文件名為 poem.txt 可以在裡面放入以下內容。

// poem.txt

Macavity - The Mystery Cat, by T. S. Eliot

Macavity's a Mystery Cat: he's called the Hidden Paw--
For he's the master criminal who can defy the Law.
He's the bafflement of Scotland Yard, the Flying Squad's despair:
For when they reach the scene of crime--Macavity's not there!

Macavity, Macavity, there's no on like Macavity,
He's broken every human law, he breaks the law of gravity.
His powers of levitation would make a fakir stare,
And when you reach the scene of crime--Macavity's not there!
You may seek him in the basement, you may look up in the air--
But I tell you once and once again, Macavity's not there!

在同一目錄中,我們將創建我們的腳本,該腳本將讀取這個詩歌文件並將其打印出來。在操作系統返回文件讀取的結果後,將在回調中為我們完成打印文件或處理錯誤。如下圖,在readFile.js ,您的回調會在異步操作系統進程返回後觸發。當此 OS 進程返回時,您提供的 Node.js 回調將放置在要處理的事件循環中,然後在循環到達該進程時執行。

然後,您的回調可以執行任何操作,從更新應用程序中的狀態,到處理錯誤(如果有),以及註銷用戶、什麼都不做,甚至完全終止 Node 進程。

// readFile.js

const fs = require('fs');

// Attempt to read the poem file
// Attach a callback to handle a successful read and print the contents to console
fs.readFile('./poem.txt', 'utf-8', function(err, data) {
    if (err) return console.error(err);

    let poem = data.toString();
    console.log('Here is the poem of the day...\n\n');
    return console.log(data);
});

使用 node readFile.js 運行此代碼 .該文件將被讀取,並且控制台應將這首詩打印回給您。如果沒有,它會打印出遇到的錯誤,例如,如果指定路徑下沒有這樣的文件。

回調適用於一次性處理數據、錯誤和事件。但是,當回調嵌套多層時,它們會變得複雜。另一種處理事件的方法是使用事件監聽器,這將在下一節中介紹。

使用事件監聽器響應事件

事件偵聽器是在特定事件類型發生時運行的函數。例如,在讀取文件、建立服務器連接或查詢數據庫時,我們使用的模塊,例如 fs , net , 或 mongoose , 都具有它們將發出的內置事件類型。

通常發出這些事件的對象擴展了基礎 EventEmitter 對象,來自內置的事件模塊。

您的應用程序可以通過事件偵聽器的機制來響應這些事件。通常,您通過關鍵字“on”在代碼中附加一個事件偵聽器,後跟一個指定事件類型的字符串,最後是一個函數,即事件發生時要運行的代碼。

為了查看事件監聽器的實際效果,我們將創建一個與 Cat API 交互並解析來自 API 的響應的服務器。然後,我們的服務器將處理請求並向訪問者顯示“每日貓”圖像。我們將使用的事件是 http 的一部分 模塊。

我們還將使用 xml2js 模塊來解析 Cat API 生成的 XML 響應。安裝 xml2js ,您將需要運行命令 npm install xml2js 在合適的項目目錄中。

安裝模塊後,在目錄中創建兩個文件 cats.html , 和 cats.js . cats.html 內 ,放置我們應用程序的前端。這將簡單地顯示我們將要解析的貓數據。

<!-- cats.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Cats</title>

    <!-- Bootstrap -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">

    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.2/html5shiv.js"></script>
      <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
    <![endif]-->
  </head>
  <body>

    <div class="container-fluid">
      <div class="col-md-8 col-md-offset-2">
        <h1>Cats Of Silicon Valley</h1>

        <h2>Welcome to the Cat Of The Day</h2>

        <img src=IMGSRC class="img-fluid" alt="Responsive image">
        <br>
        <label class="primary">Source: SOURCE</label>
        <br>
        <a href="/" class="btn btn-primary btn-lg">More Cats!</a>
      </div>
    </div>

    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
  </body>
</html>

我們的大部分邏輯將在與事件偵聽器一起使用的服務器端代碼中。這包含在 cats.js 文件。要查看正在運行的事件監聽器代碼,請將以下代碼放入文件中,然後使用 node cats.js 運行它 ,然後在瀏覽器中訪問 http://localhost:4000 .

// cat.js

const http = require('http');
const fs = require('fs');
const xml2js = require('xml2js');

// We will get images from the CatAPI https://thecatapi.com/
let catApi = 'http://thecatapi.com/api/images/get?format=xml&results_per_page=1';

let catUrl = '';
let catSource = '';

let server = http.createServer(function(req, res) {
    // Get fresh cat data from the Cat API
    http.get(catApi, (res) => {
        let data = '';
    
        // Attach event listener for when receiving data from the remote server is complete
        res.on('end', () => {
            console.log('***We have completed cat data\n***');
            console.log(data);
      
            let parser = new xml2js.Parser();
            return parser.parseString(data, function(err, imgxml) {
                if (err) {
                    return console.log('Error parsing cat data');
                } else {
                    let imgjson = JSON.parse(JSON.stringify(imgxml));
        
                    console.log('***We have cat JSON***');
                    console.log(imgjson);
        
                    catUrl = imgjson.response.data[0].images[0].image[0].url[0];
                    return catSource = imgjson.response.data[0].images[0].image[0].source_url[0];
                }
            });
        });
    
        // Event listener for the 'data' event
        // In this case, accumulate all the data so we can use it all at once later
        return res.on('data', (xml) => {
            return data += xml;
        });
    });

    // Serve cat images from the CatAPI
    return fs.readFile('./cats.html', function(err, cathtml) {
        if (err) {
            console.error(err);
            return res.end('An error occurred');
        }
    
        let html = cathtml.toString()
                          .replace('IMGSRC', catUrl)
                          .replace('SOURCE', catSource);
    
        res.writeHead(200, {
            'Content-Type': 'text/html'
        });
    
        res.write(html);
        return res.end();
    });
});

// Run the server
server.listen(4000);

下面,我們詳細進入代碼。也看看代碼中的註釋吧。

從代碼中可以看出,我們對 Cat API 的請求請求新的貓數據。然後我們讓 Node.js 繼續正常執行。但是,我們附加了兩個事件偵聽器來處理來自遠程 API 的新事件。其中第一個是“on end”事件偵聽器。當我們從 Cat API 獲得完整的 cat 有效負載時,我們會使用新數據和圖像更新我們的頁面。我們正在監聽的第二類事件是“數據”事件。當有來自遠程主機的新數據時觸發。在這種情況下,我們緩衝數據並將其添加到我們的臨時數據存儲中。

現在,借助事件偵聽器的強大功能,我們可以輕鬆地隨意獲取新的貓圖像。

我們的網站訪問者只需點擊一個按鈕即可獲得新的“每日貓”圖片。

Node.js 中的事件和計時器比我們這裡描述的要多得多。下一個值得探討的主題是事件發射器,它讓您對應用程序可以使用的事件種類擁有更多權力。


Tutorial JavaScript 教程
  1. 如何防止 React 中的重新渲染

  2. 在 react Native 中使用 new Date() 時出錯不是構造函數

  3. Observables 中的運算符和訂閱

  4. addEventListener 接受函數和(!)對象

  5. JavaScript難學嗎?

  6. 離地球最近的小行星是什麼?

  7. 帶有 JavaScript 的 CPF 和 CNPJ 生成器

  1. 如何在 JavaScript 中更改 window.location.href 然後執行更多的 JS?

  2. jQuery 測驗問題 21-30

  3. MERN 堆棧 A 到 Z:第 1 部分

  4. Twitter Bootstrap modal:如何移除 Slide down 效果

  5. 多次渲染以分離紋理

  6. 如何計算兩個日期之間的天數

  7. Angular 11 - JWT 身份驗證示例和 PHP 教程

  1. pin-latest:輕鬆解析 package.json “最新”版本

  2. 將 HMR 與 Angular 一起使用時需要注意的事項

  3. 我正在寫一本書:Gulp、Bower 和 Yeoman 的前端工具

  4. 您不知道自己需要的 10 大開發人員工具