Jak radzić sobie z cyklicznymi zależnościami w węźle.js

Ostatnio pracowałem z nodejsem i wciąż zajmuję się systemem modułów, więc przepraszam, jeśli jest to oczywiste pytanie. Chcę kod mniej więcej taki jak poniżej:

A. js (główny plik uruchamiany z węzłem)

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;

Mój problem polega na tym, że nie mogę uzyskać dostępu do instancji ClassA z poziomu instancji ClassB.

Czy istnieje poprawny / lepszy sposób na strukturę modułów, aby osiągnąć to, czego chcę? Czy istnieje lepszy sposób dzielenia zmiennych między modułami?

Author: icktoofay, 2012-06-03

12 answers

While node.js pozwala na okrągłe require zależności, jak się okazało może to być dość brudny i prawdopodobnie lepiej będzie, jeśli zmienisz swój kod, aby go nie potrzebował. Może stwórz trzecią klasę, która użyje pozostałych dwóch, aby osiągnąć to, czego potrzebujesz.

 51
Author: JohnnyHK,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-22 01:16:45

Spróbuj ustawić właściwości na module.exports, zamiast całkowicie je zastępować. Np. module.exports.instance = new ClassA() w a.js, module.exports.ClassB = ClassB w b.js. Kiedy tworzysz okrągłe zależności modułu, Moduł wymagający otrzyma odniesienie do niekompletnego module.exports z wymaganego modułu, do którego możesz dodać inne właściwości, ale gdy ustawisz cały module.exports, w rzeczywistości tworzysz nowy obiekt, do którego moduł wymagający nie ma dostępu.

 141
Author: lanzz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-03 18:54:03

[edytuj] nie jest 2015 i większość bibliotek (np. express) wprowadziła aktualizacje z lepszymi wzorcami, więc kołowe zależności nie są już potrzebne. Polecam po prostu nie używać ich .


Wiem, że szukam starej odpowiedzi... Problemem jest ten moduł.eksport jest zdefiniowany po wymagasz ClassB. (co pokazuje link JohnnyHK) Okrężne zależności działają świetnie w węzłach, są po prostu definiowane synchronicznie. Gdy są właściwie używane, faktycznie rozwiązują wiele typowych problemów z węzłami (np. dostęp do express.js app z innych plików)

Po prostu upewnij się, że eksport jest zdefiniowany przed potrzebujesz pliku z zależnością Okrężną.

To się zepsuje:

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

module.exports = ClassA;

To zadziała:

var ClassA = module.exports = function(){};
var ClassB = require('classB');
Używam tego wzoru cały czas, aby uzyskać dostęp do expressa.js app w innych plikach:
var express = require('express');
var app = module.exports = express();
// load in other dependencies, which can now require this file and use app
 41
Author: Will Stern,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-04 16:00:22

Czasami jest naprawdę sztucznie wprowadzić trzecią klasę( jak radzi JohnnyHK), więc oprócz Ianzz: Jeśli chcesz wymienić moduł.eksportuje, na przykład, jeśli tworzysz klasę (jak plik b. js w powyższym przykładzie), jest to również możliwe, po prostu upewnij się, że w pliku, który się uruchamia circular require, 'moduł.exports = ...'oświadczenie dzieje się przed oświadczeniem require.

A. js (plik główny uruchamia się z 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 ClassB = function() {
}

ClassB.prototype.doSomethingLater() {
    util.log(a.property);
}

module.exports = ClassB;

var a = require("./a"); // <------ this is the only necessary change
 27
Author: Corno,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-31 06:26:17

Rozwiązaniem jest "przekazanie deklaracji" eksportowanego obiektu przed wymaganiem jakiegokolwiek innego kontrolera. Więc jeśli uporządkujesz wszystkie swoje moduły w ten sposób i nie napotkasz żadnych problemów takich jak ten:

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

};

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

// Functions:
var foo = function () {

};

// Module exports injects:
module.exports.foo = foo;
 10
Author: Nicolas Gramlich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-18 00:35:39

Rozwiązaniem wymagającym minimalnej zmiany jest rozszerzenie module.exports zamiast nadpisanie go.

A. js-app entry point and module which use method do from 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-moduł wykorzystujący metodę do from 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 
    }
})

Będzie działać i produkować:

doing b
doing a

Podczas gdy ten kod nie będzie działał:

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

Wyjście:

node a.js
b.js:7
a.do();
    ^    
TypeError: a.do is not a function
 7
Author: setec,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-12 21:19:24

A co z leniwym wymaganiem tylko wtedy, gdy trzeba? Więc twój b. js wygląda następująco

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

Oczywiście dobrą praktyką jest umieszczanie wszystkich instrukcji require na wierzchu pliku. Ale są sytuacje, w których wybaczam sobie wybranie czegoś z innego modułu. Nazwij to hack, ale czasami jest to lepsze niż wprowadzenie dalszej zależności, dodanie dodatkowego modułu lub dodanie nowych struktur (EventEmitter, itp.)

 4
Author: zevero,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-17 13:36:28

Inną metodą, którą widziałem, jest eksportowanie w pierwszej linii i zapisywanie jej jako zmiennej lokalnej, takiej jak Ta:

let self = module.exports = {};

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

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

Używam tej metody, Czy wiesz o jej wadach?

 2
Author: Bence Gedai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-20 17:58:03

Właściwie to wymagałem uzależnienia od

 var a = null;
 process.nextTick(()=>a=require("./a")); //Circular reference!
Nie ładnie, ale działa. Jest to bardziej zrozumiałe i uczciwe niż zmiana b. js (na przykład tylko moduły rozszerzające.eksport), który w przeciwnym razie jest idealny jak jest.
 1
Author: zevero,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-17 14:01:40

Podobne do odpowiedzi lanzza i setecta, używam następującego wzoru:

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

Object.assign() kopiuje członków do obiektu exports, który został już przekazany innym modułom.

Przypisanie = jest logicznie zbędne, ponieważ jest tylko ustawieniem module.exports dla siebie, ale używam go, ponieważ pomaga mojemu IDE (WebStorm) rozpoznać, że firstMember jest właściwością tego modułu, więc "Go to -> Declaration" (Cmd-B) i inne narzędzia będą działać z innych narzędzi pliki.

Ten wzór nie jest zbyt ładny, więc używam go tylko wtedy, gdy trzeba rozwiązać problem cyklicznej zależności.

 1
Author: joeytwiddle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-05 04:40:51

Możesz to łatwo rozwiązać: po prostu wyeksportuj swoje dane, zanim będziesz potrzebować czegoś innego w modułach, w których używasz modułu."eksport": {]}

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();
 1
Author: Giuseppe Canale,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-30 09:10:19

Dla Twojego problemu możesz użyć deklaracji funkcji.

Class-b.js:

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

module.exports = ClassB

function ClassB() {

}

Class-a.js:

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

module.exports = ClassA

function ClassA() {

}
 -4
Author: Jonathan Ong,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-02 02:40:31