个性化阅读
专注于IT技术分析

如何轻松在Node.js中将事件发射器与ES5和ES6结合使用

本文概述

如今, 你可以在项目中使用许多开源库。它们非常好用, 可以让你做很多事情, 以了解事件是何时发生的。它们通常允许你向内部触发的事件添加回调。你是否曾经想过, 构建这样的允许使用回调的库的正确和最简单的方法是什么?好吧, 这是否是正确的方法并不是绝对的真理, 而是许多库依赖于Node.js的Event Emitter类。

所有发出事件的对象都是EventEmitter类的实例, 这些对象公开一个eventEmitter.on()函数, 该函数允许将一个或多个函数附加到该对象发出的命名事件。通常, 事件名称是驼峰式的字符串, 但是可以使用任何有效的JavaScript属性键。

在本文中, 你将学习如何将它们用于ECMAScript 5和6。

ES5

如果你使用的是ECMAScript 5, 那么对于Java初学者来说, 事件发射器的用法就不太清楚了:

// yourLibrary.js

// Instantiate event emitter and inherits
var EventEmitter = require('events');
var inherits = require('util').inherits;

// Create the constructor of YourLibrary and add the EventEmitter to the this context
function YourLibrary() {
    EventEmitter.call(this);
}

// Use Inheritance to add the properties of the DownloadManager to event emitter
inherits(YourLibrary, EventEmitter);

// Export YourLibrary !
module.exports = YourLibrary;

在从YourLibrary创建实例期间执行EventEmitter.call(this)时, 会将EventEmitter构造函数声明的属性附加到YourLibrary。然后, 继承函数将原型方法从一个构造函数继承到另一个构造函数(你的构造函数YourLibrary和超级构造函数EventEmitter), 这样, 你的构造函数的原型将设置为从superConstructor创建的新对象。

由于你的库显然不会提供EventEmitter的相同方法, 因此你需要通过在module.exports行之前或之后使用原型将自己的函数添加到YourLibrary中:

//yourLibrary.js

YourLibrary.prototype.testAsyncMethod = function testAsyncMethod(someData) {
    _this = this;

    // Execute the data event in 2 seconds
    setTimeout(function(){
        // Emit the data event that sends the same data providen by testAsyncMethod 
        _this.emit("data", someData);
    }, 2000);
};

上一个函数将testAsyncMethod添加到你的库中, 该方法将一些数据作为第一个参数, 并将通过data事件再次发送, 该事件是使用EventEmitter类的继承方法发出的。通过这种方式, YourLibrary使用Node.js的事件管理器, 并且可以用来创建有条理且易于阅读的代码:

// otherfile.js

// Require YourLibrary file
var YourLibrary = require("./yourLibrary");

// Create an instance of YourLibrary
var libInstance = new YourLibrary();

// Add the "data" event listener to your library and add some callback
libInstance.on("data", function(data){
    // Outputs: "Hello World, data test"
    console.log(data);
});

// Execute the testAsyncMethod of your library
libInstance.testAsyncMethod("Hello World, data test");

尽管处理异步事件可能会有些棘手, 但最后一切都会变得有意义, 因此, 如果你还不了解它, 请耐心等待并仔细分析示例。

ES6

使用EcmaScript 6, 该任务确实得到了简化, 并且比ES5更容易处理和理解。但是, 它的工作方式相同:通过使用extends关键字, 你的类可以扩展EventEmitter, 从而明显继承其方法, 因此可以使用emit方法触发事件:

const EventEmitter = require('events');

class YourLibrary extends EventEmitter {
    constructor() {
        super();
    }

    testAsyncMethod(data) {
        this.emit('data', data);
    }
}

module.exports = YourLibrary

然后, 可以从另一个文件轻松使用YourLibrary:

const MyLibrary = require('./yourLibrary');

const libInstance = new MyLibrary();

libInstance.on('data', (data) => {
    // Outputs : Received data: "Hello World, data test"
    console.log(`Received data: "${data}"`);
});

libInstance.testAsyncMethod("Hello World, data test");

如你所见, 这比使用ES6处理相同的代码要容易得多。

使用ECMAScript 5的示例

如果你没有在介绍中找到它, 请放心, 我们确信将示例作为现实世界中的库(或类似的东西)来处理将有助于你理解。在此示例中, 假设我们将使用下载管理器库, 该库确实非常简单, 它提供了一种从服务器异步下载文件的方法。使用Node.js的Event Emitters类, 你可以知道文件的下载时间, 并可以了解下载的进度。

该示例并没有真正下载任何内容, 我们只是使用setInterval(用于progress事件)和setTimeout(用于downloadSuccess事件)函数来模拟下载。在工作区的某个文件夹中创建fileDownloader.js, 并在其上添加以下代码:

// fileDownloader.js

var EventEmitter = require('events');
var inherits = require('util').inherits;

// Create the constructor of DownloadManager and add the EventEmitter to the this context
function DownloadManager() {
    EventEmitter.call(this);
}

// Use Inheritance to add the properties of the event emitter to DownloadManager
inherits(DownloadManager, EventEmitter);

// Export the Download Manager
module.exports = DownloadManager;


//
// Write your library down here by prototyping !
//

/**
 * @param URL {String} Url of the imaginary file to download
 */
DownloadManager.prototype.downloadAsync = function downloadAsync(URL) {
    var _this = this;
    var progress = 0;

    console.log('Download file "' + URL + '" ...');

    // Trigger the progress event every second
    var progressInterval = setInterval(function() {
        progress += 20;
        
        // Emit progress event with the progress as argument
        _this.emit('progress' , progress);
    }, 1000);

    // Trigger the downloadSuccess event in 5.5 seconds and clear the progress interval
    setTimeout(function() {
        var optionalDataResponse = {
            filename: "imaginary.txt", filesize: 123123, fileUrl: URL
        };

        // Stop triggering progress
        clearInterval(progressInterval);
        
        // Use the emit method of the EventEmitter to trigger the downloadSuccess event !
        _this.emit('downloadSuccess' , optionalDataResponse);
    }, 5500);
};

然后可以按以下方式使用下载管理器(在本示例中为index.js文件):

// index.js

// Require the download manager library
var DownloadManager = require("./fileDownloader");

// Create an instance of the Download Manager
var downloader = new DownloadManager();

// Add event listener of the progress of download
downloader.on("progress", function (progress){
    console.log('Download progress: '+ progress +'%');
});

// Do something when the download of the file ends
downloader.on("downloadSuccess", function (response){
    //{
    //    filename: "imaginary.txt", //    filesize: 123123
    //}
    console.log(response);
});

// Start download
downloader.downloadAsync("file.txt");

并使用节点index.js执行脚本, 你将获得以下输出:

Download file "file.txt" ...
Download progress: 20%
Download progress: 40%
Download progress: 60%
Download progress: 80%
Download progress: 100%

{ filename: 'imaginary.txt', filesize: 123123, fileUrl: 'file.txt' }

使用ECMAScript 6的示例

对于ES6中的示例, 我们将使用与ES5中第一个示例相同的想法, 假设我们将使用Download Manager库, 这个库非常简单, 它提供了一种从服务器异步下载文件的方法。使用Node.js的Event Emitters类, 你可以知道文件的下载时间, 并可以了解下载的进度。

该示例并没有真正下载任何内容, 我们只是使用setInterval(用于progress事件)和setTimeout(用于downloadSuccess事件)函数来模拟下载。在工作区的某个文件夹中创建fileDownloader.js, 并在其上添加以下代码:

const EventEmitter = require('events');

class DownloadManager extends EventEmitter {
    constructor() {
        super();
    }

    downloadAsync(URL) {
        let _this = this;
        let progress = 0;

        console.log(`Download file '${URL}' ...`);

        // Trigger the progress event every second
        let progressInterval = setInterval(() => {
            progress += 20;

            // Emit progress event with the progress as argument
            _this.emit('progress', progress);
        }, 1000);

        // Trigger the downloadSuccess event in 5.5 seconds and clear the progress interval
        setTimeout(() => {
            let optionalDataResponse = {
                filename: "imaginary.txt", filesize: 123123, fileUrl: URL
            };

            // Stop triggering progress
            clearInterval(progressInterval);

            // Use the emit method of the EventEmitter to trigger the downloadSuccess event !
            _this.emit('downloadSuccess', optionalDataResponse);
        }, 5500);
    }
}

module.exports = DownloadManager

下载管理器可用于其他文件(index.js):

// index.js

// Require the download manager library
const DownloadManager = require('./fileDownloader');

// Create an instance of the Download Manager
const downloader = new DownloadManager();

// Add event listener of the progress of download
downloader.on("progress", (progress) => {
    console.log(`Download progress ${progress}%`);
});

// Do something when the download of the file ends
downloader.on("downloadSuccess", (response) => {
    //{
    //    filename: "imaginary.txt", //    filesize: 123123
    //}
    console.log(response);
});

// Start download
downloader.downloadAsync("file.txt");

使用节点index.js执行脚本, 你将获得以下输出(与ES5中的示例相同):

Download file "file.txt" ...
Download progress: 20%
Download progress: 40%
Download progress: 60%
Download progress: 80%
Download progress: 100%

{ filename: 'imaginary.txt', filesize: 123123, fileUrl: 'file.txt' }

在使用Node.js的EventEmitter类时, 你可以使用库中此类的所有方法, 因此不要忘记查看Node.js的文档以了解有关事件发射器的更多信息。

编码愉快!

赞(0)
未经允许不得转载:srcmini » 如何轻松在Node.js中将事件发射器与ES5和ES6结合使用

评论 抢沙发

评论前必须登录!