web-dev-qa-db-fra.com

Comment traiter les dépendances cycliques dans Node.js

Je travaille avec nodejs ces derniers temps et je commence toujours à comprendre le système de modules. Je veux un code à peu près comme ci-dessous:

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

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 un moyen correct/meilleur de structurer les modules pour atteindre ce que je veux? Existe-t-il un meilleur moyen de partager des variables entre les modules?

144
Runcible

Bien que node.js autorise les dépendances circulaires require, comme vous l'avez constaté, il peut être assez compliqué et il est probablement préférable de restructurer votre code pour ne pas en avoir besoin. Peut-être créer une troisième classe qui utilise les deux autres pour accomplir ce dont vous avez besoin.

74
JohnnyHK

Essayez de définir les propriétés sur module.exports Au lieu de les remplacer complètement. Par exemple, module.exports.instance = new ClassA() dans a.js, module.exports.ClassB = ClassB Dans b.js. Lorsque vous créez des dépendances de module circulaires, le module requis obtient une référence à un module.exports Incomplet à partir du module requis, auquel vous pouvez ajouter d'autres propriétés, mais lorsque vous définissez l'intégralité de module.exports, vous créez en fait un nouvel objet auquel le module requis n'a aucun moyen d'accéder.

165
lanzz

Parfois, il est vraiment artificiel d'introduire une troisième classe (comme JohnnyHK le conseille), donc en plus de Ianzz: Si vous souhaitez remplacer le module.exports, par exemple si vous créez une classe (comme le fichier b.js dans l’exemple ci-dessus), c’est également possible, assurez-vous simplement que, dans le fichier qui commence l’exigence circulaire, l’instruction 'module.exports = ...' est antérieure à l’instruction require.

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

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
30
Coen

La solution consiste à "déclarer en aval" votre objet d'export avant de faire appel à un autre contrôleur. Donc, si vous structurez tous vos modules de cette manière et que vous ne rencontrerez aucun problème de ce type:

// Module exports forward declaration:
module.exports = {

};

// Controllers:
var other_module = require('./other_module');

// Functions:
var foo = function () {

};

// Module exports injects:
module.exports.foo = foo;
14
Nicolas Gramlich

Une solution qui nécessite un minimum de modifications est l'extension de module.exports Au lieu de la remplacer.

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

Cela fonctionnera et produira:

doing b
doing a

Bien que ce code ne fonctionne 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
7
setec

Qu'en est-il des besoins paresseux seulement quand vous en avez besoin? Donc, 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 sûr, il est judicieux de placer toutes les déclarations require sur le fichier. Mais il y a sont occasions, où je me pardonne d'avoir choisi quelque chose d'un module par ailleurs non apparenté. Appelez cela un piratage, mais cela vaut parfois mieux que d'introduire une dépendance supplémentaire, d'ajouter un module supplémentaire ou d'ajouter de nouvelles structures (EventEmitter, etc.)

5
zevero

Une autre méthode que j'ai déjà vue consiste à exporter à la première ligne et à l'enregistrer en tant que variable locale, comme ceci:

let self = module.exports = {};

const a = require('./a');

// Exporting the necessary functions
self.func = function() { ... }

J'ai tendance à utiliser cette méthode, connaissez-vous des inconvénients?

4
Bence Gedai

Vous pouvez résoudre cela facilement: exportez simplement vos données avant de demander quoi que ce soit 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();
3
Giuseppe Canale

En fait, j'ai fini par avoir besoin de ma dépendance avec

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

ce n'est pas beau, mais ça marche. C'est plus compréhensible et honnête que de changer b.js (par exemple, en augmentant uniquement modules.export), ce qui est parfait autrement.

1
zevero

Voici une solution de contournement rapide dont l'utilisation est complète.

Dans le fichier 'a.js'

let B;
class A{
  constructor(){
    process.nextTick(()=>{
      B = require('./b')
    })
  } 
}
module.exports = new A();

Sur le fichier 'b.js', écrivez ce qui suit

let A;
class B{
  constructor(){
    process.nextTick(()=>{
      A = require('./a')
    })
  } 
}
module.exports = new B();

Ainsi, lors de la prochaine itération des classes de boucle d’événement, les classes seront définies correctement et les instructions require fonctionneront comme prévu.

1
Melik Karapetyan

Semblable aux réponses de lanzz et de setect, j’utilise le modèle suivant:

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

La Object.assign() copie les membres dans l'objet exports déjà attribué à d'autres modules.

L’affectation = Est logiquement redondante, puisqu’elle se définit elle-même avec module.exports, Mais je l’utilise car elle aide mon IDE (WebStorm) à reconnaître que firstMember est une propriété de ce module, donc "Aller à -> Déclaration" (Cmd-B) et d'autres outils fonctionneront à partir d'autres fichiers.

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

1
joeytwiddle