Как работать с циклическими зависимостями в Node.js

Я работал с nodejs в последнее время и все еще получаю доступ к модульной системе, поэтому извиняюсь, если это очевидный вопрос. Я хочу код примерно следующим образом:

a.js (основной файл работает с 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;

Моя проблема заключается в том, что я не могу получить доступ к экземпляру ClassA из экземпляра ClassB.

Есть ли правильный/лучший способ структурирования модулей для достижения того, что я хочу? Есть ли лучший способ обмена переменными между модулями?

Ответ 1

В то время как node.js разрешает круговые зависимости require, поскольку вы обнаружили, что это может быть довольно грязно, и вам, вероятно, лучше провести реструктуризацию код не нужен. Возможно, создайте третий класс, который использует два других, чтобы выполнить то, что вам нужно.

Ответ 2

Попробуйте установить свойства на module.exports вместо того, чтобы полностью его заменить. Например, module.exports.instance = new ClassA() в a.js, module.exports.ClassB = ClassB в b.js. Когда вы выполняете круговые зависимости модуля, требуемый модуль получит ссылку на неполный module.exports из требуемого модуля, в который вы можете добавить другие свойства, но при установке всего module.exports вы фактически создаете новый объект к которому не нужен доступный модуль.

Ответ 3

[EDIT] это не 2015 год, и большинство библиотек (т.е. экспресс) сделали обновления с лучшими шаблонами, поэтому круговые зависимости больше не нужны. Я рекомендую просто не использовать их.


Я знаю, что я выкапываю старый ответ здесь... Проблема здесь в том, что module.exports определяется после того, как вам требуется ClassB. (что показывает ссылка JohnnyHK) Круговые зависимости отлично работают в Node, они просто определяются синхронно. При правильном использовании они фактически решают множество общих проблем node (например, обращение к express.js app из других файлов)

Просто убедитесь, что ваш необходимый экспорт определен до того, как вам понадобится файл с циклической зависимостью.

Это сломается:

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

module.exports = ClassA;

Это будет работать:

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

Я использую этот шаблон все время для доступа к express.js app в других файлах:

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

Ответ 4

Иногда действительно искусственно вводить третий класс (как советует JohnnyHK), поэтому в дополнение к Ianzz: Если вы хотите заменить module.exports, например, если вы создаете класс (например, файл b.js в приведенном выше примере), это также возможно, просто убедитесь, что в файле, который запускает round require, оператор 'module.exports =...' встречается перед оператором require.

a.js (основной файл работает с 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

Ответ 5

Решение заключается в том, чтобы "переслать объявление" вашего объекта экспорта, прежде чем требовать какой-либо другой контроллер. Поэтому, если вы структурируете все свои модули, как это, и вы не столкнетесь с такими проблемами:

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

};

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

// Functions:
var foo = function () {

};

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

Ответ 6

Решение, требующее минимального изменения, расширяется module.exports вместо его переопределения.

a.js - точка входа и модуль приложения, которые используют метод из 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 - модуль, который использует метод из 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 
    }
})

Он будет работать и производить:

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

Вывод:

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

Ответ 7

Как насчет ленивых, требующих только тогда, когда вам нужно? Итак, ваш b.js выглядит следующим образом

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

Конечно, хорошая практика заключается в том, чтобы все запросы требовать поверх файла. Но бывают случаи, когда я прощаю себя за то, что выбрал что-то из другого, не связанного с ним модуля. Назовите это взломом, но иногда это лучше, чем введение дополнительной зависимости, или добавление дополнительного модуля или добавление новых структур (EventEmitter и т.д.).

Ответ 8

Другой метод, который я видел, это делать, экспортируя в первой строке и сохраняя его как локальную переменную следующим образом:

let self = module.exports = {};

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

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

Я использую этот метод, вы знаете о каких-либо его недостатках?

Ответ 9

Вы можете легко решить эту проблему: просто экспортируйте свои данные, прежде чем требовать что-либо еще в модулях, где вы используете 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();

Ответ 10

Подобно ответам lanzz и setect, я использовал следующий шаблон:

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

Object.assign() копирует элементы в объект exports, который уже был передан другим модулям.

Назначение = логически избыточно, поскольку оно просто устанавливает module.exports для себя, но я использую его, потому что он помогает моей среде IDE (WebStorm) распознавать, что firstMember является свойством этого модуля, поэтому "Перейти к → Декларация" (Cmd-B) и другие инструменты будут работать из других файлов.

Этот шаблон не очень красив, поэтому я использую его только тогда, когда необходимо решить проблему с циклической зависимостью.

Ответ 11

Вообще-то я в конечном итоге нуждался в моей зависимости от

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

не очень, но он работает. Это более понятно и честно, чем изменение b.js(например, только увеличение modules.export), что в противном случае является совершенным как есть.

Ответ 12

Вот быстрый обходной путь, который я нашел полным.

В файле "a.js"

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

В файле "b.js" напишите следующее

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

Таким образом, на следующей итерации классы цикла событий будут определены правильно, и те операторы require будут работать как положено.

Ответ 13

для вашей проблемы, вы можете использовать объявления функций.

класс-b.js:

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

module.exports = ClassB

function ClassB() {

}

класс-a.js:

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

module.exports = ClassA

function ClassA() {

}