2011-02-02 24 views
75

Nel mio sistema, ho un certo numero di "classi" caricate nel browser ciascuna un file separato durante lo sviluppo e concatenate insieme per la produzione. Come si sono caricati, si inizializzare una proprietà su un oggetto globale, qui G, come in questo esempio:Come gestire le dipendenze circolari con RequireJS/AMD?

var G = {}; 

G.Employee = function(name) { 
    this.name = name; 
    this.company = new G.Company(name + "'s own company"); 
}; 

G.Company = function(name) { 
    this.name = name; 
    this.employees = []; 
}; 
G.Company.prototype.addEmployee = function(name) { 
    var employee = new G.Employee(name); 
    this.employees.push(employee); 
    employee.company = this; 
}; 

var john = new G.Employee("John"); 
var bigCorp = new G.Company("Big Corp"); 
bigCorp.addEmployee("Mary"); 

Invece di utilizzare il mio oggetto globale, sto considerando di rendere ogni classe di una propria AMD module, sulla base di James Burke's suggestion:

define("Employee", ["Company"], function(Company) { 
    return function (name) { 
     this.name = name; 
     this.company = new Company(name + "'s own company"); 
    }; 
}); 
define("Company", ["Employee"], function(Employee) { 
    function Company(name) { 
     this.name = name; 
     this.employees = []; 
    }; 
    Company.prototype.addEmployee = function(name) { 
     var employee = new Employee(name); 
     this.employees.push(employee); 
     employee.company = this; 
    }; 
    return Company; 
}); 
define("main", ["Employee", "Company"], function (Employee, Company) { 
    var john = new Employee("John"); 
    var bigCorp = new Company("Big Corp"); 
    bigCorp.addEmployee("Mary"); 
}); 

il problema è che prima, non vi era alcuna dipendenza dichiarare-tempo tra dipendenti e azienda: si potrebbe mettere la dichiarazione in qualsiasi ordine si voleva, ma ora, con RequireJS, questo introduce una dipendenza, che è qui (intenzionalmente) circolare, quindi il codice sopra riportato fallisce. Naturalmente, in addEmployee(), aggiungere una prima riga var Employee = require("Employee"); sarebbe make it work, ma vedo questa soluzione come inferiore a non utilizzare RequireJS/AMD in quanto richiede a me, lo sviluppatore, di essere a conoscenza di questa dipendenza circolare appena creata e di fare qualcosa al riguardo.

Esiste un modo migliore per risolvere questo problema con RequireJS/AMD oppure sto utilizzando RequireJS/AMD per qualcosa per cui non è stato progettato?

risposta

59

Questa è davvero una restrizione nel formato AMD. Potresti usare le esportazioni e quel problema sparirà. Trovo le esportazioni di essere brutto, ma è come regolare i moduli CommonJS risolvono il problema:

define("Employee", ["exports", "Company"], function(exports, Company) { 
    function Employee(name) { 
     this.name = name; 
     this.company = new Company.Company(name + "'s own company"); 
    }; 
    exports.Employee = Employee; 
}); 
define("Company", ["exports", "Employee"], function(exports, Employee) { 
    function Company(name) { 
     this.name = name; 
     this.employees = []; 
    }; 
    Company.prototype.addEmployee = function(name) { 
     var employee = new Employee.Employee(name); 
     this.employees.push(employee); 
     employee.company = this; 
    }; 
    exports.Company = Company; 
}); 

In caso contrario, la richiedono ("Dipendente") si parla nel messaggio avrebbe funzionato troppo.

In generale con i moduli è necessario essere più consapevoli delle dipendenze circolari, AMD o meno. Anche in un semplice JavaScript, devi essere sicuro di usare un oggetto come l'oggetto G nell'esempio.

+3

ho pensato che tu avessi dichiarare esportazioni in entrambe le richiamate lista di argomenti, come 'function (exports, Company)' e 'function (exports, Employee)'. Comunque, grazie per RequireJS, è stupendo. –

+0

@jrburke Penso che questo possa essere fatto in modo unidirezionale, per un mediatore o core o altro componente top-down? È un'idea terribile, per renderla accessibile usando entrambi i metodi? http://stackoverflow.com/questions/11264827/circular-dependencies-in-modules-using-requirejs/17666014#17666014 – SimplGy

+1

Non sono sicuro di aver capito come questo risolva il problema. La mia comprensione è che tutte le dipendenze devono essere caricate prima dell'esecuzione di define. Non è questo il caso se "esportazioni" viene passato come prima dipendenza? –

15

Penso che questo sia uno svantaggio in progetti più grandi in cui le dipendenze circolari (multi-livello) non vengono rilevate. Tuttavia, con madge è possibile stampare un elenco di dipendenze circolari per accedervi.

madge --circular --format amd /path/src 
+0

cool Ecco a voi il check-out –

+0

CACSVML -13295: sc-admin-ui-express amills001c $ madge --circular --format amd ./ Nessuna dipendenza circolare trovata! –

7

Se non è necessario le dipendenze da caricare alla partenza (per esempio, quando si sta estendendo una classe), allora questo è ciò che si può fare: (tratto da http://requirejs.org/docs/api.html#circular)

In il file a.js:

define([ 'B' ], function(B){ 

     // Just an example 
     return B.extend({ 
      // ... 
     }) 

    }); 

E nell'altro file b.js:

define([ ], function(){ // Note that A is not listed 

     var a; 
     require(['A'], function(A){ 
      a = new A(); 
     }); 

     return function(){ 
      functionThatDependsOnA: function(){ 
       // Note that 'a' is not used until here 
       a.doStuff(); 
      } 
     }; 

    }); 
.210

Nell'esempio del PO, questo è come cambierebbe:

define("Employee", [], function() { 

     var Company; 
     require(["Company"], function(C){ 
      // Delayed loading 
      Company = C; 
     }); 

     return function (name) { 
      this.name = name; 
      this.company = new Company(name + "'s own company"); 
     }; 
    }); 

    define("Company", ["Employee"], function(Employee) { 
     function Company(name) { 
      this.name = name; 
      this.employees = []; 
     }; 
     Company.prototype.addEmployee = function(name) { 
      var employee = new Employee(name); 
      this.employees.push(employee); 
      employee.company = this; 
     }; 
     return Company; 
    }); 

    define("main", ["Employee", "Company"], function (Employee, Company) { 
     var john = new Employee("John"); 
     var bigCorp = new Company("Big Corp"); 
     bigCorp.addEmployee("Mary"); 
    }); 
+2

Come ha detto Gili nel suo commento, questa soluzione è sbagliata e non funzionerà sempre. C'è una condizione di competizione su cui il blocco di codice verrà eseguito per primo. –

5

voglio solo evitare la dipendenza circolare. Forse qualcosa di simile:

G.Company.prototype.addEmployee = function(employee) { 
    this.employees.push(employee); 
    employee.company = this; 
}; 

var mary = new G.Employee("Mary"); 
var bigCorp = new G.Company("Big Corp"); 
bigCorp.addEmployee(mary); 

non credo che sia una buona idea per risolvere questo problema e cercare di mantenere la dipendenza circolare. Mi sembra una cattiva pratica generale. In questo caso, può funzionare perché richiede realmente quei moduli per quando viene chiamata la funzione esportata.Ma immagina il caso in cui i moduli sono richiesti e utilizzati nella definizione stessa delle funzioni. Nessuna soluzione farà funzionare. Questo è probabilmente il motivo per cui require.js fallisce rapidamente nel rilevamento delle dipendenze circolari nelle dipendenze della funzione di definizione.

Se si deve davvero aggiungere un problema, l'IMO più pulito deve richiedere una dipendenza appena in tempo (nelle funzioni esportate in questo caso), quindi le funzioni di definizione funzioneranno correttamente. Ma anche l'IMO più pulito è solo per evitare del tutto le dipendenze circolari, il che è davvero facile da fare nel tuo caso.

+1

Si suggerisce di semplificare un modello di dominio e renderlo meno utilizzabile solo perché lo strumento requirejs non lo supporta. Gli strumenti dovrebbero semplificare la vita dello sviluppatore. Il modello di dominio è piuttosto semplice: dipendente e azienda. L'oggetto dipendente dovrebbe sapere quale società per cui lavora, le aziende dovrebbero avere un elenco di dipendenti. Il modello di dominio è giusto, è lo strumento che fallisce qui – Dethariel

5

Tutte le risposte postate (eccetto lo https://stackoverflow.com/a/25170248/14731) sono errate. Anche la documentazione ufficiale (a novembre 2014) è sbagliata.

L'unica soluzione che ha funzionato per me è dichiarare un file "gatekeeper" e farlo definire un metodo che dipende dalle dipendenze circolari. Vedi https://stackoverflow.com/a/26809254/14731 per un esempio concreto.


Ecco perché le soluzioni di cui sopra non funzioneranno.

  1. Non è possibile:
var a; 
require(['A'], function(A){ 
    a = new A(); 
}); 

e quindi utilizzare a in seguito, perché non v'è alcuna garanzia che questo blocco di codice otterrà eseguito prima del blocco di codice che utilizza a. (Questa soluzione è fuorviante perché funziona il 90% delle volte)

  1. Non vedo motivi per ritenere che exports non sia vulnerabile alla stessa condizione di competizione.

la soluzione a questo è:

//module A 

    define(['B'], function(b){ 

     function A(b){ console.log(b)} 

     return new A(b); //OK as is 

    }); 


//module B 

    define(['A'], function(a){ 

     function B(a){} 

     return new B(a); //wait...we can't do this! RequireJS will throw an error if we do this. 

    }); 


//module B, new and improved 
    define(function(){ 

     function B(a){} 

     return function(a){ //return a function which won't immediately execute 
       return new B(a); 
     } 

    }); 

ora possiamo usare questi moduli A e B nel modulo C

//module C 
    define(['A','B'], function(a,b){ 

     var c = b(a); //executes synchronously (no race conditions) in other words, a is definitely defined before being passed to b 

    }); 
+0

btw, se hai ancora problemi con questo, la risposta di @ yeahdixon dovrebbe essere corretta, e penso che la documentazione stessa sia corretta. –

+0

Sono d'accordo sul fatto che la vostra metodologia funziona, ma ritengo che la documentazione sia corretta e potrebbe essere un passo avanti rispetto a "sincrono". –

5

Guardai i documenti sul dipendenze circolari: http://requirejs.org/docs/api.html#circular

Se esiste una dipendenza circolare con a e b, nel modulo viene richiesto di aggiungere require come dipendenza nel modulo l ike così:

define(["require", "a"],function(require, a) { .... 

poi quando si ha bisogno di "un" solo chiamata "a" in questo modo:

return function(title) { 
     return require("a").doSomething(); 
    } 

Questo ha funzionato per me

+0

questo ha funzionato anche per me –

Problemi correlati