2012-06-03 26 views
87

Ultimamente sto lavorando con nodejs e sto ancora prendendo confidenza con il sistema del modulo, quindi mi scuso se questa è una domanda ovvia. Voglio il codice più o meno simile al seguente qui sotto:Come gestire le dipendenze cicliche in Node.js

a.js (la corsa file principale con il 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; 

mio problema sembra essere che non riesco ad accedere all'istanza di ClassA da un'istanza di ClassB.

Esiste un modo corretto/migliore per strutturare i moduli per ottenere ciò che voglio? C'è un modo migliore per condividere le variabili tra i moduli?

+0

Ti suggerisco di controllare la separazione delle query, il pattern osservabile e poi quello che i CS chiamano manager - che è fondamentalmente un wrapper per il pattern osservabile. – dewwwald

risposta

39

Mentre node.js consente le dipendenze circolari da require, come hai trovato può essere pretty messy e probabilmente stai meglio ristrutturando il tuo codice per non averne bisogno. Magari creare una terza classe che usi gli altri due per ottenere ciò di cui hai bisogno.

+3

+1 Questa è la risposta giusta. Le dipendenze circolari sono odore di codice. Se A e B sono sempre usati insieme, sono effettivamente un singolo modulo, quindi uniscili. O trovare un modo per rompere la dipendenza; forse è un modello composito. – James

+49

Non sempre. nei modelli di database, ad esempio, se ho i modelli A e B, nel modello A potrei voler fare riferimento al modello B (ad es. per unire le operazioni) e viceversa. Pertanto, esportare diverse proprietà A e B (quelle che non dipendono da altri moduli) prima di utilizzare la funzione "richiede" potrebbe essere una risposta migliore. – Joaobrunoah

+6

Anche io non vedo dipendenze circolari come odore di codice. Sto sviluppando un sistema in cui ci sono alcuni casi in cui è necessario. Ad esempio, team di modellazione e utenti, in cui gli utenti possono appartenere a molti team. Quindi, non è che qualcosa sia sbagliato con la mia modellazione. Ovviamente, potrei rifattorizzare il mio codice per evitare la dipendenza circolare tra le due entità, ma quella non sarebbe la forma più pura del modello di dominio, quindi non lo farò. –

106

Provare a impostare le proprietà su module.exports, invece di sostituirlo completamente. Ad esempio, module.exports.instance = new ClassA() in a.js, module.exports.ClassB = ClassB in b.js. Quando si creano dipendenze di moduli circolari, il modulo richiesto otterrà un riferimento a un module.exports incompleto dal modulo richiesto, a cui è possibile aggiungere altre proprietà, ma quando si imposta l'intero module.exports, si crea effettivamente un nuovo oggetto che richiede il modulo non ha modo di accedere.

+17

Questa dovrebbe essere la risposta giusta! –

+3

Questo potrebbe essere tutto vero, ma direi comunque evitare le dipendenze circolari. Prendere accordi speciali per gestire i moduli che hanno suoni incompleti come quelli che creeranno un problema futuro che non si desidera avere. Questa risposta prescrive una soluzione su come affrontare i moduli caricati in modo incompleto ... Non penso sia una buona idea. –

+0

Come metteresti un costruttore di classi in 'module.esporta senza sostituirlo completamente, per consentire alle altre classi di "costruire" un'istanza della classe? –

24

A volte è davvero artificiale introdurre una terza classe (come consiglia JohnnyHK), quindi oltre a Ianzz: Se si desidera sostituire il modulo.esporta, ad esempio se si sta creando una classe (come la b.js file nell'esempio precedente), anche questo è possibile, ma assicurati che nel file che sta iniziando la richiesta circolare, l'istruzione 'module.exports = ...' venga eseguita prima dell'istruzione require.

a.js (la corsa file principale con il 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 
+0

grazie a coen, non mi ero mai reso conto che module.exports aveva un effetto su circular dipendenze. –

36

[EDIT] Non è il 2015 e la maggior parte delle librerie (cioè esprimere) hanno ha apportato aggiornamenti con modelli migliori in modo che le dipendenze circolari non siano più necessarie. Raccomando semplicemente di non usarli.


so che sto scavando un vecchio risposta qui ... Il problema qui è che module.exports è definito dopo si richiede ClassB. (che mostra il collegamento di JohnnyHK) Le dipendenze circolari funzionano perfettamente nel nodo, sono definite solo in modo sincrono. Se utilizzati correttamente, risolvono in realtà molti problemi dei nodi comuni (come l'accesso a Express.js app da altri file)

Accertarsi che le esportazioni necessarie siano definite prima del si richiede un file con una dipendenza circolare.

Questo si romperà:

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

module.exports = ClassA; 

questo funzionerà:

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

Io uso questo modello per tutto il tempo per l'accesso alle express.js app in altri file:

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

Una soluzione che richiede un cambiamento minimo si estende module.exports invece di overr iding esso.

a.js - punto di ingresso app e dei moduli che utilizzano il metodo fanno da 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 - modulo che utilizzano il metodo fanno da 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 
    } 
}) 

sarà lavorare e produrre:

doing b 
doing a 

Anche se questo codice non funzionerà:

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

uscita:

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

Questa è l'unica risposta che ha funzionato per me in questa discussione. – sevensevens

+2

Se non si ha 'underscore', quindi 'Object.assign()' di ES6 può fare lo stesso lavoro che '_.extend()' sta facendo in questa risposta. – joeytwiddle

9

La soluzione è quella di ' avanti dichiara 'il tuo oggetto di esportazione prima di richiedere qualsiasi altro controller. Quindi se strutturate tutti i moduli in questo modo e non vi imbatterete in alcun problema del genere:

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

}; 

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

// Functions: 
var foo = function() { 

}; 

// Module exports injects: 
module.exports.foo = foo; 
+1

In realtà, questo mi ha portato a usare semplicemente 'exports.foo = function() {...}'. Sicuramente ha fatto il trucco. Grazie! – zanona

-4

per il vostro problema, è possibile utilizzare le dichiarazioni di funzione.

classe-b.js:

var ClassA = require('./class-a') 

module.exports = ClassB 

function ClassB() { 

} 

classe-a.js:

var classB = require('./class-b') 

module.exports = ClassA 

function ClassA() { 

} 
1

Simile a lanzz e risposte di setect, ho utilizzato il seguente schema:

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

Il Object.assign() copia i membri nell'oggetto exports che è già stato assegnato ad altri mesi Dules.

L'assegnazione = è logicamente superfluo, dal momento che è appena impostazione module.exports a se stessa, ma Io lo utilizzo perché aiuta mio IDE (WebStorm) a riconoscere che firstMember è una struttura di questo modulo, in modo da "Go To -> Dichiarazione "(Cmd-B) e altri strumenti funzioneranno da altri file.

Questo modello non è molto carino, quindi lo uso solo quando è necessario risolvere un problema di dipendenza ciclica.

0

Che dire di pigro che richiede solo quando è necessario? Quindi il tuo b.js ha il seguente aspetto:

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

Ovviamente è buona norma mettere tutte le istruzioni obbligatorie in cima al file. Ma ci sono occasioni, in cui mi perdono per aver scelto qualcosa da un modulo altrimenti non correlato. Chiamatelo un hack, ma a volte questo è meglio che l'introduzione di un ulteriore dipendenza, o l'aggiunta di un modulo aggiuntivo o l'aggiunta di nuove strutture (EventEmitter, ecc)

0

In realtà ho finito per richiedere la mia dipendenza con

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

carino, ma funziona. È più comprensibile e onesto della modifica di b.js (ad esempio, solo l'aumento di moduli.export), che altrimenti è perfetto così com'è.

+0

Di tutte le soluzioni in questa pagina, questo è l'unico che ha risolto il mio problema. Ci ho provato a turno. –

1

Un altro metodo che ho visto gente esporta in prima linea e salvandolo come variabile locale come questo:

let self = module.exports = {}; 

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

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

Io tendo ad usare questo metodo, si fa a sapere su eventuali aspetti negativi di vero?