Node.js में चक्रीय निर्भरता से कैसे निपटें


162

मैं हाल ही में नोड्ज के साथ काम कर रहा हूं और अभी भी मॉड्यूल सिस्टम के साथ पकड़ में आ रहा हूं इसलिए अगर यह एक स्पष्ट सवाल है तो माफी मांगें। मुझे नीचे दिए गए कोड की तरह कोड चाहिए:

a.js (नोड के साथ मुख्य फ़ाइल रन)

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;

मेरी समस्या यह प्रतीत होती है कि मैं ClassB के उदाहरण से ClassA के उदाहरण तक नहीं पहुँच सकता।

क्या संरचना मॉड्यूल का एक सही / बेहतर तरीका है जो मुझे चाहिए? क्या मॉड्यूल के बीच चर साझा करने का एक बेहतर तरीका है?


मेरा सुझाव है कि आप क्वेरी सेपरेशन, ऑब्जर्वेबल पैटर्न को कमांड करने के लिए देखें और फिर सीएस लोग मैनेजर्स को क्या कहते हैं - जो मूल रूप से ऑब्जर्वेबल पैटर्न के लिए एक आवरण है।
देवलवल्ड

जवाबों:


86

जबकि नोड.जेएस परिपत्र requireनिर्भरता की अनुमति देता है, जैसा कि आपने पाया है कि यह बहुत गड़बड़ हो सकता है और आप शायद इसकी आवश्यकता नहीं करने के लिए अपने कोड को पुनर्गठन से बेहतर कर रहे हैं। हो सकता है कि एक तीसरा वर्ग बनाएं जो अन्य दो का उपयोग करता है जो आपको चाहिए।


6
+1 यह सही उत्तर है। परिपत्र निर्भरताएं कोड गंध हैं। यदि A और B हमेशा एक साथ उपयोग किए जाते हैं तो वे प्रभावी रूप से एक एकल मॉड्यूल हैं, इसलिए उन्हें मर्ज करें। या निर्भरता को तोड़ने का एक तरीका खोजें; शायद इसका एक समग्र स्वरूप।
जेम्स

94
हर बार नहीं। डेटाबेस मॉडल में, उदाहरण के लिए, यदि मेरे पास मॉडल ए और बी है, तो मॉडल एआई मॉडल बी (जैसे संचालन में शामिल होने के लिए), और इसके विपरीत संदर्भ लेना चाह सकता है। इसलिए, "आवश्यकता" फ़ंक्शन का उपयोग करने से पहले कई ए और बी गुणों (जो अन्य मॉड्यूल पर निर्भर नहीं करते हैं) को निर्यात करें, यह एक बेहतर जवाब हो सकता है।
जोओ ब्रूनो अबू हेटम डी लिज़

11
मुझे कोड गंध के रूप में परिपत्र निर्भरता भी दिखाई नहीं देती है। मैं एक ऐसी प्रणाली विकसित कर रहा हूँ जहाँ कुछ मामले हैं जहाँ इसकी आवश्यकता है। उदाहरण के लिए, मॉडलिंग टीम और उपयोगकर्ता, जहाँ उपयोगकर्ता कई टीमों से संबंधित हो सकते हैं। इसलिए, ऐसा नहीं है कि मेरी मॉडलिंग में कुछ गड़बड़ है। जाहिर है, मैं दो संस्थाओं के बीच परिपत्र निर्भरता से बचने के लिए अपना कोड रिफलेक्टर कर सकता हूं, लेकिन यह डोमेन मॉडल का सबसे शुद्ध रूप नहीं होगा, इसलिए मैं ऐसा नहीं करूंगा।
अलेक्जेंड्रे मार्टिनी

1
फिर क्या जरूरत पड़ने पर मुझे निर्भरता का इंजेक्शन देना चाहिए, क्या आपका मतलब है? चक्रीय समस्या के साथ दो निर्भरता के बीच बातचीत को नियंत्रित करने के लिए एक तीसरे का उपयोग करना?
जियोनीडीपंड्स

2
यह गड़बड़ नहीं है .. कोड ia एकल फ़ाइल की पुस्तक से बचने के लिए कोई व्यक्ति फ़ाइल को ब्रेक करना चाह सकता है। जैसा कि नोड बताता है कि आपको exports = {}अपने कोड के शीर्ष पर और फिर अपने कोड exports = yourDataके अंत में जोड़ना चाहिए । इस अभ्यास से आप परिपत्र निर्भरता से लगभग सभी त्रुटियों से बचेंगे।
पुजारी

178

module.exportsपूरी तरह से बदलने के बजाय, गुणों को सेट करने का प्रयास करें । जैसे, module.exports.instance = new ClassA()में a.js, module.exports.ClassB = ClassBमें b.js। जब आप परिपत्र मॉड्यूल निर्भरता बनाते हैं, तो module.exportsआवश्यक मॉड्यूल को आवश्यक मॉड्यूल से अपूर्ण का संदर्भ मिलेगा , जिसे आप बाद में अन्य गुणों को जोड़ सकते हैं, लेकिन जब आप संपूर्ण सेट करते हैं module.exports, तो आप वास्तव में एक नया ऑब्जेक्ट बनाते हैं जिसमें आवश्यक मॉड्यूल नहीं होता है पहुंचने का रास्ता।


6
यह सब सच हो सकता है, लेकिन मैं कहूंगा कि अभी भी परिपत्र निर्भरता से बचें। मॉड्यूल से निपटने के लिए विशेष इंतजाम करना, जिसमें अधूरी तरह से भरी हुई आवाजें हों जैसे कि यह एक भविष्य की समस्या पैदा करेगा जो आप नहीं चाहते हैं। यह उत्तर एक समाधान का वर्णन करता है कि अधूरे लोड किए गए मॉड्यूल से कैसे निपटें ... मुझे नहीं लगता कि यह एक अच्छा विचार है।
अलेक्जेंडर मिल्स

1
आप module.exportsपूरी तरह से इसे बदलने के बिना एक वर्ग के निर्माणकर्ता को कैसे डालेंगे, अन्य वर्गों को कक्षा का एक उदाहरण 'निर्माण' करने की अनुमति देने के लिए?
टिम विज़े

1
मुझे नहीं लगता कि आप कर सकते हैं। आपके मॉड्यूल को पहले से ही आयात करने वाले मॉड्यूल उस परिवर्तन को नहीं देख पाएंगे
lanzz

52

[संपादित करें] यह २०१५ नहीं है और अधिकांश पुस्तकालयों (यानी एक्सप्रेस) ने बेहतर पैटर्न के साथ अपडेट किए हैं इसलिए परिपत्र निर्भरता अब आवश्यक नहीं है। मेरा सुझाव है कि बस उनका उपयोग न करें


मुझे पता है कि मैं यहाँ एक पुराना उत्तर खोद रहा हूँ ... यहाँ मुद्दा यह है कि ClassB की आवश्यकता होने के बाद मॉड्यूल.एक्सपोर्ट को परिभाषित किया गया है । (जो जॉनीएचके के लिंक से पता चलता है) परिपत्र निर्भरताएँ नोड में महान काम करती हैं, वे बस तुल्यकालिक रूप से परिभाषित होती हैं। जब सही तरीके से उपयोग किया जाता है, तो वे वास्तव में बहुत सारे सामान्य नोड मुद्दों को हल करते हैं (जैसे 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');

मैं एक्सप्रेस तक पहुँचने के लिए हर समय इस पैटर्न का उपयोग करता हूँ app। अन्य फाइलों में:

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

2
पैटर्न साझा करने के लिए धन्यवाद और फिर आगे साझा करते हुए कि आप निर्यात करते समय आमतौर पर इस पैटर्न का उपयोग कैसे करते हैंapp = express()
user566245

34

कभी-कभी यह तीसरी श्रेणी (जैसा कि जॉनीएचके सलाह देता है) शुरू करने के लिए वास्तव में कृत्रिम है, इसलिए इनाज़ के अतिरिक्त: यदि आप मॉड्यूल को बदलना चाहते हैं। तो उदाहरण के लिए, यदि आप एक वर्ग बना रहे हैं (जैसे कि b.js फ़ाइल में उपरोक्त उदाहरण), यह भी संभव है, बस यह सुनिश्चित करें कि जिस फ़ाइल में परिपत्र आवश्यकता शुरू हो रही है, वह 'mod.exports = ...' कथन आवश्यक कथन से पहले होता है।

a.js (नोड के साथ मुख्य फ़ाइल रन)

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

धन्यवाद, मुझे पता ही नहीं चला कि मॉड्यूल.एक्सपोर्ट का परिपत्र निर्भरता पर प्रभाव था।
लॉरेंट पेरिन

यह विशेष रूप से Mongoose (MongoDB) मॉडल के साथ उपयोगी है; एक समस्या को ठीक करने में मेरी मदद करता है जब BlogPost मॉडल में टिप्पणियों के संदर्भ के साथ एक सरणी होती है, और प्रत्येक टिप्पणी मॉडल में BlogPost का संदर्भ होता है।
ओलेग ज़ेरेवेन्नी

14

समाधान किसी अन्य नियंत्रक की आवश्यकता से पहले अपने निर्यात वस्तु को 'आगे घोषित' करने के लिए है। इसलिए यदि आप इस तरह से अपने सभी मॉड्यूलों की संरचना करते हैं और आप इस तरह के किसी भी मुद्दे पर नहीं चलेंगे:

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

};

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

// Functions:
var foo = function () {

};

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

3
वास्तव में, इसने मुझे केवल exports.foo = function() {...}इसके बजाय उपयोग करने के लिए प्रेरित किया । निश्चित रूप से चाल चली। धन्यवाद!
ज़नोना

मुझे यकीन नहीं है कि आप यहाँ क्या प्रस्तावित कर रहे हैं। module.exportsडिफ़ॉल्ट रूप से पहले से ही एक सादा वस्तु है, इसलिए आपकी "आगे की घोषणा" लाइन बेमानी है।
ZachB

7

एक समाधान जिसके लिए न्यूनतम परिवर्तन की आवश्यकता होती है, 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

4
यदि आपके पास नहीं है underscore, तो ES6 Object.assign()वही काम _.extend()कर सकता है जो इस उत्तर में कर रहा है।
joeytwiddle 6

5

जब आपको आवश्यकता हो तभी आलसी को क्या चाहिए? तो आपका 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, आदि)


और कभी-कभी यह महत्वपूर्ण होता है जब एक माता-पिता के संदर्भ को बनाए रखने वाली बाल वस्तुओं के साथ एक पेड़ डेटा संरचना से निपटना। पारितोषिक के लिए धन्यवाद।
रॉबर्ट ओस्लर

5

एक अन्य विधि जिसे मैंने देखा है कि लोग पहली पंक्ति में निर्यात कर रहे हैं और इसे इस तरह से स्थानीय चर के रूप में सहेज रहे हैं:

let self = module.exports = {};

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

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

मैं इस पद्धति का उपयोग करता हूं, क्या आप इसके किसी भी डाउनसाइड के बारे में जानते हैं?


आप बल्कि कर सकते हैं module.exports.func1 = ,module.exports.func2 =
अश्वनी अग्रवाल

4

आप इसे आसानी से हल कर सकते हैं: अपने डेटा को निर्यात करने से पहले आपको मॉड्यूल में कुछ और की आवश्यकता होती है जहां आप मॉड्यूल का उपयोग करते हैं। निर्यात:

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

लैन्ज़ और सेट के उत्तरों के समान, मैं निम्नलिखित पैटर्न का उपयोग कर रहा हूं:

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

Object.assign()प्रतियों में सदस्यों exportsउद्देश्य यह है कि पहले से ही अन्य मॉड्यूल को दिया गया है।

=काम तार्किक बेमानी है, क्योंकि यह सिर्फ स्थापित कर रही है module.exportsअपने आप को, लेकिन मैं इसे का उपयोग कर रहा है क्योंकि यह मदद करता है मेरी आईडीई (WebStorm) पहचान करने के लिए है कि firstMemberइस मॉड्यूल के एक संपत्ति है, इसलिए "to go -> घोषणा" (Cmd-बी) और अन्य टूलिंग अन्य फ़ाइलों से काम करेगी।

यह पैटर्न बहुत सुंदर नहीं है, इसलिए मैं इसका उपयोग केवल तब करता हूं जब चक्रीय निर्भरता समस्या को हल करने की आवश्यकता होती है।


2

यहाँ एक त्वरित वर्कअराउंड है जो मैंने पाया है कि पूर्ण का उपयोग करें।

फ़ाइल '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();

इवेंट लूप कक्षाओं के अगले पुनरावृत्ति पर इस तरह से सही ढंग से परिभाषित किया जाएगा और उन बयानों की आवश्यकता होगी जो अपेक्षित रूप से काम करेंगे।


1

वास्तव में मैंने अपनी निर्भरता की आवश्यकता को समाप्त कर दिया

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

सुंदर नहीं है, लेकिन यह काम करता है। यह b.js (उदाहरण के लिए केवल मॉड्यूल.export बदलने के लिए) की तुलना में अधिक समझने योग्य और ईमानदार है, जो अन्यथा के रूप में एकदम सही है।


इस पृष्ठ के सभी समाधानों में से, यह एकमात्र समस्या है जिसने मेरी समस्या को हल किया। मैंने बारी-बारी से कोशिश की।
जो लैप

0

इससे बचने का एक तरीका यह है कि किसी एक फाइल को दूसरे में डालने की जरूरत न पड़े, इसे एक फंक्शन के तर्क के रूप में पास करें जो आपको कभी भी दूसरी फाइल में चाहिए। इस तरह से परिपत्र निर्भरता कभी नहीं पैदा होगी।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.