219 votes

Comment gérer les dépendances cycliques dans Node.js ?

J'ai travaillé avec nodejs Ces derniers temps, je suis encore en train de me familiariser avec la module Je vous prie donc de m'excuser si cette question est évidente. J'ai besoin d'un code qui ressemble à peu près à ce qui suit :

a.js (le fichier principal exécuté avec node)

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;

Mon problème semble être que je ne peux pas accéder à l'instance de ClassA à partir d'une instance de ClassB.

Existe-t-il une manière correcte / meilleure de structurer modules pour obtenir ce que je veux ? Existe-t-il une meilleure façon de partager variables à travers modules ?

11voto

zevero Points 29

Qu'en est-il de la paresse qui consiste à ne demander que lorsque c'est nécessaire ? Ainsi, votre b.js ressemble à ce qui suit

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

Bien entendu, il est préférable de placer toutes les instructions requises en tête du fichier. Mais il y a sont les occasions où je me pardonne d'avoir choisi quelque chose dans un module qui n'a rien à voir. Appelez ça un hack, mais parfois c'est mieux que d'introduire une dépendance supplémentaire, ou d'ajouter un module supplémentaire ou d'ajouter de nouvelles structures (EventEmitter, etc).

11voto

Giuseppe Canale Points 46

Vous pouvez résoudre ce problème facilement : exportez vos données avant de demander quoi que ce soit d'autre dans les modules où vous utilisez 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();

9voto

Joe Blow Points 3618

la solution extrêmement simple est souvent :

en général, l'exigence se trouve au début du fichier ...

var script = require('./script')
function stuff() {
      script.farfunction()
}

au lieu de cela, il suffit de l'exiger "dans la fonction"

function stuff() {
      var _script = require('./script')
      _script.farfunction()
}

8voto

setec Points 1355

Une solution qui ne nécessite qu'un minimum de changements est l'extension module.exports au lieu de la remplacer.

a.js - point d'entrée de l'application et module qui utilise la méthode 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 - module qui utilise la méthode 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 
    }
})

Il fonctionnera et produira :

doing b
doing a

Ce code ne fonctionnera pas :

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();

Sortie :

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

8voto

joeytwiddle Points 3226

L'important est de ne pas réaffecter les module.exports que l'on vous a donné, car cet objet peut déjà avoir été donné à d'autres modules dans le cycle ! Il suffit d'assigner des propriétés à l'intérieur de module.exports et d'autres modules les verront apparaître.

La solution la plus simple est donc la suivante :

module.exports.firstMember = ___;
module.exports.secondMember = ___;

Le seul véritable inconvénient est la nécessité de répéter l'opération. module.exports. à plusieurs reprises.


Comme dans les réponses de lanzz et de setec, j'ai utilisé le modèle suivant, qui semble plus déclaratif :

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

Les Object.assign() copie les membres dans la base de données exports qui a déjà été donné à d'autres modules.

Les = est logiquement redondante, puisqu'il s'agit simplement de définir module.exports à lui-même, mais je l'utilise parce qu'il aide mon IDE (WebStorm) à reconnaître que firstMember est une propriété de ce module, de sorte que "Go To -> Declaration" (Cmd-B) et d'autres outils fonctionneront à partir d'autres fichiers.

Ce modèle n'est pas très joli, c'est pourquoi je ne l'utilise que lorsqu'un problème de dépendance cyclique doit être résolu.

Il est assez bien adapté à la révéler le motif car il est facile d'ajouter et de supprimer des exportations de l'objet, en particulier lorsque l'on utilise la fonction abréviation de la propriété .

Object.assign(module.exports, {
    firstMember,
    //secondMember,
});

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X