如何创建可重用的Socket.IO模块

时间:2016-05-16 20:41:59

标签: javascript node.js sockets dependency-injection socket.io

我遇到了创建模块的麻烦,该模块公开了我的Socket.IO库的功能:

const sio = require('socket.io');
module.exports = function(server) {
  const io = sio(server);
  return {
    register: function(namespace) {
      let nsp = io.of(namespace);
      nsp.on('connect', function(socket) {
        // ...
      }
    }
  }
}

现在的问题是如何在其他模块中使用它?在我的app.js

我使用Express创建server并且可以使用require('./mysocketio')(server)实例化模块,但不能在其他模块中实例化,因为服务器在那里不可用。什么是解决这些循环依赖的好方法?

4 个答案:

答案 0 :(得分:3)

你可以通过各种方式实现这些目标,例如:

  • 将对象设置为全局命名空间。 (改变全球需求护理)
  • 使用module.exports并要求其他文件中的对象。 (如果做得不好,可能导致循环依赖问题)
  • 将实例作为参数传递给控制器​​,同时在路由中要求它们。

myModule.js 公开Socket.IO库功能的模块

const sio = require('socket.io');
module.exports = function(server) {
  const io = sio(server);
  return {
    register: function(namespace) {
      let nsp = io.of(namespace);
      nsp.on('connect', function(socket) {
        // ...
      }
    }
  }
}

FLOW 1:将模块设置为全局命名空间。

<强> app.js

var app = require('express').createServer();
var io = require('./myModule')(app);
global._io = io;

app.listen(80)

<强> controller.js

module.exports = function(io){
    var that={};
    /*
     * Private local variable
     * made const so that 
     * one does not alter it by mistake
     * later on.
     */
    const _io = global._io; 

    that.myAction = function(req,res){

        _io.register('newRoom');
        res.send('Done');   
    }
    return that;
}

流程2:将模块作为参数传递。

<强> app.js

var app = require('express').createServer();
var io = require('./myModule')(app);

require(./router.js)(app,io);

app.listen(80);

<强> router.js

/*
 * Contains the routing logic
 */
module.exports = function (app,io) {
//passing while creating the instance of controller for the first time.
var controller = require("./controller")(io);

app.get('/test/about',controller.myAction);
};

<强> controller.js

module.exports = function(io){
    var that={};

    const _io = io; 

    that.myAction = function(req,res){

        _io.register('newsRoom');
        res.send('Done');   
    }

    // everything attached to that will be exposed
    // more like making public member functions and properties.
    return that;
}

流程3 :将io设置为全局。因此,无需每次都通过服务器。

<强> app.js

var app = require('express').createServer();
require('./myModule')(app);

require(./router.js)(app);

app.listen(80);

<强> controller.js

// no need to pass the server as io is already initialized
const _io = require('./myModule')();

module.exports = function(io){
    var that={};

    that.myAction = function(req,res){

        _io.register('newsRoom');
        res.send('Done');   
    }
    return that;
}

<强> myModule.js

module.exports = function( server ) {

  const _io = global._io || require('socket.io')(server);

  if(global._io === undefined){
    //initializing io for future use
    global._io = _io;
  }

  return {
    register: function(namespace) {
      let nsp = _io.of(namespace);
      nsp.on('connect', function(socket) {
        // ...
      }
    }
  }
}
  

可能最简单的方法是传递作为控制器的参数,同时在路由中要求它们。虽然第3流看起来很有希望,但是在改变全局命名空间时应该非常小心。

答案 1 :(得分:2)

这不是一个循环的依赖;只是你的模块a)依赖于另一个不是全局可用的模块而且b)你的模块可能在代码的许多地方使用。

<强>全球

一个可能的解决方案(有缺点),只需加载您的模块一次,并将其附加到全局: global.mysocketio = require('./mysocketio')(server);

这使您可以在项目加载后的任何位置访问global.mysocketio。这是我个人用于自己的记录器构造的结构;我的记录器在我的代码周围的许多地方使用,所以我只是将它连接到global.log。

然而,全局变量的使用有点脏;它给出了命名空间分离的问题(某些代码决定使用global.mysocketio本身的某些地方),它会创建一个“不可见”的依赖关系;其他代码只是假设某个全局存在,并且找到这些依赖关系并不容易。

导出

更好的解决方案是在需要的地方传递变量。有很多方法可以做到这一点。我知道你的app.js没有可用的服务器变量,但肯定会以某种方式包含你的快速代码。如果你需要app.js提供的'server'或'mysocketio',只需从你创建'server'的模块中导出它。像:

module.exports.expressServerVar = server;

只需2美分;你是否强烈反对我或者我错过了重要的事情?让我知道!

答案 2 :(得分:1)

我会使用工厂或依赖注入。您可以使用类似jimple的内容。

但这是一个不使用任何外部依赖的示例。这绝不是最好的代码示例,但它应该有希望得到重点。我仍然建议使用jimple而不是这个。

// app.js

var express = require('express');
var app = express();

var factory = require('./factory.js');
factory.setExpress(app); // This could also be done in the factory constructor. Or you could instanciate your express app in the factory.js class.


// factory.js
var socketIoModule = require('./your-socket-io-module.js')

function Factory() {

}

Factory.prototype.setExpress = function(app) {
    this.app = app;
}

Factory.prototype.getSocketIOModule = function() {
    return socketIoModule(this.app);
}

// By exporting it this way we are making it a singleton
// This means that each module that requires this file will
// get the same instance of factory.
module.exports = new Factory();


// some code that needs socket io module
var factory = require('./factory.js');

function() {
    var socketIo = factory.getSocketIOModule();

    socketIo.doStuff();
}

答案 3 :(得分:1)

我在我的应用程序中使用的方法是从启动脚本公开serverio个实例并在模块中重用它们

// Setup servers.
var http = require('http').Server(app);
var io = require('socket.io')(http);

// Setup application.
require('./server/app')(app, express, io);

// Start listening on port.
http.listen(configs.PORT, function() {
    console.log("Listening on " + configs.PORT);
});

在您的模块中,您可以使用io实例来设置事件处理程序或发出事件,类似这样的事情

module.exports = {
  contest: function(io, contest) {
    var namespace = io.of('/' + contest.id);
    namespace.on('connection', function(socket) {
        socket.on('word', function(data) {
            ...
        });
    });
  }
};

适用于您的样本

我会将此部分放在app.js或用于启动服务器的js文件中

const sio = require('socket.io');
const io = sio(server);

并且会有像这样的Socket.IO模块

module.exports = function(server, io) {
  return {
    register: function(namespace) {
      let nsp = io.of(namespace);
      nsp.on('connect', function(socket) {
        // ...
      }
    }
  }
}

我的样本