node.js module require cyclic-reference cyclic-dependency

Cómo lidiar con dependencias cíclicas en Node.js



module require (12)

Últimamente he estado trabajando con nodejs y todavía me estoy familiarizando con el sistema de módulos, así que me disculpo si esta es una pregunta obvia. Quiero un código más o menos como el siguiente:

a.js (el archivo principal se ejecuta con el nodo)

var ClassB = require("./b"); var ClassA = function() { this.thing = new ClassB(); this.property = 5; } var a = new ClassA(); module.exports = a;

b.js

var a = require("./a"); var ClassB = function() { } ClassB.prototype.doSomethingLater() { util.log(a.property); } module.exports = ClassB;

Mi problema parece ser que no puedo acceder a la instancia de ClassA desde dentro de una instancia de ClassB.

¿Existe una forma correcta / mejor de estructurar los módulos para lograr lo que quiero? ¿Hay una mejor manera de compartir variables entre módulos?


¿Qué pasa con la pereza que requiere solo cuando lo necesita? Entonces tu b.js se ve de la siguiente manera

var ClassB = function() { } ClassB.prototype.doSomethingLater() { var a = require("./a"); //a.js has finished by now util.log(a.property); } module.exports = ClassB;

Por supuesto, es una buena práctica colocar todas las declaraciones requeridas en la parte superior del archivo. Pero hay ocasiones en las que me perdono a mí mismo por elegir algo de un módulo que de otro modo no estaría relacionado. Llámelo un truco, pero a veces esto es mejor que introducir una dependencia adicional, o agregar un módulo adicional o agregar nuevas estructuras (EventEmitter, etc.)


A veces es realmente artificial introducir una tercera clase (como aconseja JohnnyHK), por lo que además de Ianzz: si quieres reemplazar las exportaciones de módulos, por ejemplo, si estás creando una clase (como el archivo b.js en el ejemplo anterior), esto también es posible, solo asegúrese de que en el archivo que está comenzando la circular requiera, la instrucción ''module.exports = ...'' suceda antes que la instrucción require.

a.js (el archivo principal se ejecuta con el nodo)

var ClassB = require("./b"); var ClassA = function() { this.thing = new ClassB(); this.property = 5; } var a = new ClassA(); module.exports = a;

b.js

var ClassB = function() { } ClassB.prototype.doSomethingLater() { util.log(a.property); } module.exports = ClassB; var a = require("./a"); // <------ this is the only necessary change


En realidad, terminé requiriendo mi dependencia con

var a = null; process.nextTick(()=>a=require("./a")); //Circular reference!

No es bonita, pero funciona. Es más comprensible y honesto que cambiar b.js (por ejemplo, solo aumentar módulos.exportación), que de otro modo es perfecto como es.


Intente establecer propiedades en module.exports , en lugar de reemplazarlo por completo. Por ejemplo, module.exports.instance = new ClassA() en a.js , module.exports.ClassB = ClassB en b.js Cuando module.exports dependencias de módulos circulares, el módulo module.exports obtendrá una referencia a un module.exports incompleto. module.exports desde el módulo requerido, en el que puede agregar otras propiedades más module.exports , pero cuando establece todo el module.exports , realmente crea un nuevo objeto que el módulo requerido no tiene forma de acceder.


La solución es ''anunciar declarar'' su objeto de exportación antes de requerir cualquier otro controlador. Así que si estructura todos sus módulos de esta manera y no se encontrará con ningún problema como ese:

// Module exports forward declaration: module.exports = { }; // Controllers: var other_module = require(''./other_module''); // Functions: var foo = function () { }; // Module exports injects: module.exports.foo = foo;


Otro método que he visto hacer es exportar en la primera línea y guardarlo como una variable local como esta:

let self = module.exports = {}; const a = require(''./a''); // Exporting the necessary functions self.func = function() { ... }

Tiendo a usar este método, ¿conoces algún inconveniente?


Puede resolver esto fácilmente: solo exporte sus datos antes de requerir algo más en los módulos donde usa module.exports:

classA.js

class ClassA { constructor(){ ClassB.someMethod(); ClassB.anotherMethod(); }; static someMethod () { console.log( ''Class A Doing someMethod'' ); }; static anotherMethod () { console.log( ''Class A Doing anotherMethod'' ); }; }; module.exports = ClassA; var ClassB = require( "./classB.js" ); let classX = new ClassA();

classB.js

class ClassB { constructor(){ ClassA.someMethod(); ClassA.anotherMethod(); }; static someMethod () { console.log( ''Class B Doing someMethod'' ); }; static anotherMethod () { console.log( ''Class A Doing anotherMethod'' ); }; }; module.exports = ClassB; var ClassA = require( "./classA.js" ); let classX = new ClassB();


Si bien node.js permite dependencias de require circulares, como has descubierto, puede ser bastante desordenado y probablemente sea mejor que reestructure tu código para que no lo necesite. Tal vez crees una tercera clase que usa las otras dos para lograr lo que necesitas.


Similar a las respuestas de lanzz y setect, he estado usando el siguiente patrón:

module.exports = Object.assign(module.exports, { firstMember: ___, secondMember: ___, });

Object.assign() copia los miembros en el objeto de exports que ya se ha proporcionado a otros módulos.

La asignación = es lógicamente redundante, ya que solo está configurando module.exports para sí mismo, pero lo estoy usando porque ayuda a mi IDE (WebStorm) a reconocer que firstMember es una propiedad de este módulo, por lo que "Ir a -> Declaración" (Cmd-B) y otras herramientas funcionarán desde otros archivos.

Este patrón no es muy bonito, así que solo lo uso cuando se necesita resolver un problema de dependencia cíclica.


Una solución que requiere un cambio mínimo es ampliar module.exports lugar de module.exports .

a.js - punto de entrada de la aplicación y módulo que usan el método do de b.js *

_ = require(''underscore''); //underscore provides extend() for shallow extend b = require(''./b''); //module `a` uses module `b` _.extend(module.exports, { do: function () { console.log(''doing a''); } }); b.do();//call `b.do()` which in turn will circularly call `a.do()`

b.js - módulo que usa el método do de a.js

_ = require(''underscore''); a = require(''./a''); _.extend(module.exports, { do: function(){ console.log(''doing b''); a.do();//Call `b.do()` from `a.do()` when `a` just initalized } })

Funcionará y producirá:

doing b doing a

Si bien este código no funcionará:

a.js

b = require(''./b''); module.exports = { do: function () { console.log(''doing a''); } }; b.do();

b.js

a = require(''./a''); module.exports = { do: function () { console.log(''doing b''); } }; a.do();

Salida:

node a.js b.js:7 a.do(); ^ TypeError: a.do is not a function


[EDIT] no es 2015 y la mayoría de las bibliotecas (es decir, express) han realizado actualizaciones con mejores patrones, por lo que las dependencias circulares ya no son necesarias. Recomiendo simplemente no usarlos .

Sé que estoy desenterrando una vieja respuesta aquí ... El problema aquí es que module.exports se define después de que requiera ClassB. (que muestra el enlace de JohnnyHK) Las dependencias circulares funcionan muy bien en el nodo, simplemente se definen de forma sincrónica. Cuando se usan correctamente, resuelven muchos problemas comunes de los nodos (como acceder a la app express.js desde otros archivos)

Solo asegúrese de que sus exportaciones necesarias estén definidas antes de requerir un archivo con una dependencia circular.

Esto se romperá:

var ClassA = function(){}; var ClassB = require(''classB''); //will require ClassA, which has no exports yet module.exports = ClassA;

Esto funcionará:

var ClassA = module.exports = function(){}; var ClassB = require(''classB'');

Utilizo este patrón todo el tiempo para acceder a la app express.js en otros archivos:

var express = require(''express''); var app = module.exports = express(); // load in other dependencies, which can now require this file and use app


para su problema, puede usar declaraciones de funciones.

class-b.js:

var ClassA = require(''./class-a'') module.exports = ClassB function ClassB() { }

clase-a.js:

var classB = require(''./class-b'') module.exports = ClassA function ClassA() { }