2011-02-02 19 views
75

W moim systemie, mam kilka „klas” załadowane w przeglądarce każdy oddzielnych plikach w trakcie rozwoju i sklejone do produkcji. Ponieważ są one ładowane one zainicjować własność na globalnym obiektu, tutaj G, jak w poniższym przykładzie:Jak obsługiwać zależności cykliczne z 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"); 

Zamiast własną globalny obiekt, Zastanawiam się, aby każda klasa własny AMD module, na podstawie 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"); 
}); 

kwestia jest taka, że ​​wcześniej nie było uzależnienie stwierdzenie w czasie między pracownikiem a Spółką: można umieścić deklarację, w dowolnej kolejności chciał, ale teraz, przy użyciu RequireJS ta wprowadza zależność, która jest tutaj (celowo) okrągły, więc powyższy kod kończy się niepowodzeniem. Oczywiście w addEmployee() dodanie pierwszej linii var Employee = require("Employee"); spowoduje make it work, ale uważam, że to rozwiązanie jest gorsze od używania RequireJS/AMD, ponieważ wymaga ode mnie, programisty, bycia świadomym tej nowo utworzonej zależności cyklicznej i zrobienia czegoś z tym.

Czy istnieje lepszy sposób na rozwiązanie tego problemu z RequireJS/AMD, lub używam RequireJS/AMD czegoś nie został zaprojektowany do?

Odpowiedz

59

To rzeczywiście ograniczenie w formacie AMD. Możesz użyć eksportu, a ten problem zniknie. Uważam eksportu być brzydki, ale to jest jak regularny moduły CommonJS rozwiązania problemu:

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

Inaczej, require („Pracownik”) można wymienić w wiadomości będzie działać zbyt.

Ogólnie z modułów trzeba być bardziej świadomi wzajemnie od siebie zależnych, AMD czy nie. Nawet w prostym JavaScriptu, musisz koniecznie użyć obiektu, takiego jak obiekt G w twoim przykładzie.

+3

Myślałem, trzeba było zadeklarować eksportu w obu wywołań zwrotnych lista argumentów, np. 'funkcja (eksport, firma)' i 'funkcja (eksport, pracownik)'. W każdym razie, dzięki dla RequireJS, to jest niesamowite. –

+0

@jrburke Myślę, że można to zrobić w jednokierunkowy sposób, w przypadku mediatora, rdzenia lub innego odgórnego komponentu? Czy to okropny pomysł, aby był dostępny za pomocą obu metod? http://stackoverflow.com/questions/11264827/circular-dependencies-in-modules-using-requirejs/17666014#17666014 – SimplGy

+1

Nie jestem pewien, czy rozumiem, jak to rozwiązuje problem. Rozumiem, że wszystkie zależności muszą być załadowane przed uruchomieniem definiowania. Czy nie jest tak, jeśli "eksport" jest przekazywany jako pierwsza zależność? –

15

myślę, że to jest dość wadą w większych projektach, gdzie (wielopoziomowe) okrągłe zależności mieszkają niezauważony. Jednak z madge można wydrukować listę wzajemnie od siebie zależnych do nich zbliżyć.

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

fajne Zamierzam to sprawdzić –

+0

CACSVML -13295: sc-admin-ui-express amills001c $ madge --circular --format amd ./ Nie znaleziono zależności cyklicznych! –

7

Jeśli nie potrzebuję twoich zależności zostać załadowany na początku (na przykład, gdy są rozszerzające klasę), to jest to, co można zrobić: (zaczerpnięte z http://requirejs.org/docs/api.html#circular)

W plik a.js:

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

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

    }); 

A w innym pliku 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(); 
      } 
     }; 

    }); 

W przykładzie PO, jest to, jak to zmienić:

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

Jak powiedział Gili w swoim komentarzu, to rozwiązanie jest złe i nie zawsze będzie działać. Istnieje warunek wyścigu, w którym blok kodu zostanie wykonany jako pierwszy. –

5

Chciałbym po prostu uniknąć zależność cykliczną. Może coś takiego:

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

Nie sądzę, że to dobry pomysł, aby obejść ten problem i starają się utrzymać zależność cykliczna. Po prostu czuje się jak ogólna zła praktyka. W tym przypadku może działać, ponieważ naprawdę potrzebujesz tych modułów, gdy wywoływana funkcja jest wywoływana.Ale wyobraź sobie przypadek, w którym moduły są wymagane i używane w samych rzeczywistych funkcjach definicji. Żadne obejście tego nie uczyni. Prawdopodobnie dlatego require.js szybko ulega awarii w wykrywaniu zależności cyklicznych w zależnościach funkcji definicji.

Jeśli naprawdę trzeba dodać coś do pracy, czystsze jedno IMO wymaga zależności w czasie (w tym przypadku w wyeksportowanych funkcjach), wtedy funkcje definicji będą działały poprawnie. Ale nawet bardziej czysta IMO jest po prostu unikaniem okrągłych zależności, co wydaje się bardzo łatwe w twoim przypadku.

+1

Sugerujesz uprościć model domeny i uczynić go mniej użytecznym tylko dlatego, że narzędzie requirejs tego nie obsługuje. Narzędzia mają ułatwić życie programistom. Model domeny jest dość prosty - pracownik i firma. Pracownik powinien wiedzieć, w której firmie pracuje, firmy powinny mieć listę pracowników. Model domeny ma rację, to narzędzie nie powiedzie się tutaj, – Dethariel

5

Wszystkie wysłane odpowiedzi (z wyjątkiem https://stackoverflow.com/a/25170248/14731) są nieprawidłowe. Nawet oficjalna dokumentacja (stan na listopad 2014 r.) Jest błędna.

Jedynym rozwiązaniem, które zadziałało, jest zadeklarowanie pliku "gatekeeper" i zdefiniowanie dowolnej metody zależnej od zależności cyklicznych. Zobacz https://stackoverflow.com/a/26809254/14731 dla konkretnego przykładu.


Oto dlaczego powyższe rozwiązania nie będą działać.

  1. Nie można:
var a; 
require(['A'], function(A){ 
    a = new A(); 
}); 

a następnie użyć a później, ponieważ nie ma gwarancji, że ten blok kodu dostanie wykonywany przed blokiem kodu, który używa a. (To rozwiązanie wprowadza w błąd, ponieważ działa w 90% przypadków)

  1. Nie widzę powodu, aby sądzić, że exports nie jest podatny na ten sam stan wyścigu.

rozwiązanie to:

//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); 
     } 

    }); 

teraz można wykorzystać te moduły A i B w module 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, jeśli nadal masz z tym problemy, odpowiedź @ yeahdixon powinna być poprawna, i myślę, że sama dokumentacja jest poprawna. –

+0

Zgadzam się, że twoja metodologia działa, ale uważam, że dokumentacja jest poprawna i może być o krok bliżej "synchronicznej". –

5

I spojrzał na Dokumenty na okrągłych zależności: http://requirejs.org/docs/api.html#circular

Jeśli istnieje zależność cykliczna za pomocą aib, w module mówi się, aby dodać wymaganie jako zależność w module l ike tak:

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

wtedy, gdy trzeba „a” zadzwoń „a” tak:

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

ten pracował dla mnie

+0

to działało również dla mnie –