2011-02-02 15 views
75

En mi sistema, tengo varias "clases" cargadas en el navegador, cada una en un archivo por separado durante el desarrollo, y se unen para su producción. A medida que se cargan, que inicializar una propiedad de un objeto global, aquí G, como en este ejemplo:¿Cómo manejar las dependencias circulares 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"); 

En lugar de utilizar mi propio objeto global, estoy considerando hacer cada clase su propio AMD module, basado en 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"); 
}); 

la cuestión es que antes, no había ninguna dependencia en tiempo de declarar entre el empleado y la empresa: se puede poner la declaración en el orden que quería, pero ahora, el uso de RequireJS, esto introduce una dependencia, lo cual está aquí (intencionalmente) circular, por lo que el código anterior falla. Por supuesto, en addEmployee(), agregando una primera línea var Employee = require("Employee"); sería make it work, pero veo esta solución como inferior a no usar RequireJS/AMD ya que me requiere, el desarrollador, estar al tanto de esta dependencia circular recién creada y hacer algo al respecto.

¿Existe alguna forma mejor de resolver este problema con RequireJS/AMD, o estoy usando RequireJS/AMD para algo para lo que no fue diseñado?

Respuesta

59

Esto es de hecho una restricción en el formato AMD. Puede usar exportaciones, y ese problema desaparece. Encuentro exportaciones que ser feo, pero es la forma regular de módulos CommonJS resuelven el 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; 
}); 

De lo contrario, la requieren ("Empleado") que usted menciona en su mensaje que funcionaría también.

En general, con los módulos debe tener más en cuenta las dependencias circulares, AMD o no. Incluso en JavaScript claro, debes asegurarte de usar un objeto como el objeto G en tu ejemplo.

+3

pensé que tenía que declarar las exportaciones en ambos devoluciones de llamada lista de argumentos, como 'function (exports, Company)' y 'function (exports, Employee)'. De todos modos, gracias por RequireJS, es impresionante. –

+0

@jrburke Creo que esto se puede hacer en una dirección correcta, para un mediador o núcleo u otro componente de arriba hacia abajo? ¿Es esta una idea terrible, para hacerla accesible usando ambos métodos? http://stackoverflow.com/questions/11264827/circular-dependencies-in-modules-using-requirejs/17666014#17666014 – SimplGy

+1

No estoy seguro de entender cómo esto resuelve el problema. Según entiendo, todas las dependencias deben cargarse antes de las ejecuciones de definición. ¿No es ese el caso si se pasa "exportaciones" como la primera dependencia? –

15

Creo que esto es un gran inconveniente en proyectos más grandes donde las dependencias circulares (multiniveles) no se detectan. Sin embargo, con madge puede imprimir una lista de dependencias circulares para abordarlas.

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

cool Voy a verificar esto –

+0

CACSVML -13295: sc-admin-ui-express amills001c $ madge --circular --format amd ./ ¡No se encontraron dependencias circulares! –

7

Si no necesita sus dependencias que se cargue en el inicio (por ejemplo, cuando se está extendiendo una clase), entonces esto es lo que puede hacer: (tomado de http://requirejs.org/docs/api.html#circular)

En el archivo a.js:

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

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

    }); 

Y en el otro archivo 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(); 
      } 
     }; 

    }); 

En el ejemplo de la OP, esta es la forma en que iba a cambiar:

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

Como dijo Gili en su comentario, esta solución es incorrecta y no siempre funcionará. Hay una condición de carrera en la que se ejecutará primero el bloque de código. –

5

me acaba de evitar la dependencia circular. Tal vez algo como:

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

no creo que es una buena idea para solucionar este problema y tratar de mantener la dependencia circular. Simplemente se siente como mala práctica general. En este caso, puede funcionar porque realmente necesita esos módulos para cuando se llama a la función exportada.Pero imagine el caso donde los módulos son requeridos y usados ​​en las funciones de definición propiamente dichas. Ninguna solución lo hará funcionar. Esa es probablemente la razón por la cual require.js falla rápidamente en la detección de dependencia circular en las dependencias de la función de definición.

Si realmente tiene que sumar un inconveniente, la IMO más limpia necesita una dependencia justo a tiempo (en este caso, en las funciones exportadas), entonces las funciones de definición funcionarán correctamente. Pero incluso una IMO más limpia es solo para evitar las dependencias circulares en conjunto, lo que se siente realmente fácil de hacer en su caso.

+1

Usted sugiere simplificar un modelo de dominio y hacerlo menos útil solo porque la herramienta requirejs no lo admite. Se supone que las herramientas facilitan la vida del desarrollador. El modelo de dominio es bastante simple: empleado y compañía. El objeto del empleado debe saber para qué compañía (s) trabaja, las compañías deben tener una lista de empleados. El modelo de dominio es correcto, es la herramienta que falla aquí – Dethariel

5

Todas las respuestas publicadas (excepto https://stackoverflow.com/a/25170248/14731) son incorrectas. Incluso la documentación oficial (desde noviembre de 2014) es incorrecta.

La única solución que funcionó para mí es declarar un archivo "gatekeeper" y hacer que defina cualquier método que dependa de las dependencias circulares. Ver https://stackoverflow.com/a/26809254/14731 para un ejemplo concreto.


He aquí por qué las soluciones anteriores no funcionarán.

  1. Usted no puede:
var a; 
require(['A'], function(A){ 
    a = new A(); 
}); 

y luego usar a más adelante, porque no hay ninguna garantía de que este bloque de código se ejecutan antes del bloque de código que utiliza a. (Esta solución es engañosa, ya que funciona el 90% de las veces)

  1. no veo ninguna razón para creer que exports no es vulnerable a la misma condición de carrera.

la solución a esto es:

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

    }); 

Ahora podemos utilizar estos módulos A y B en el módulo 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

por cierto, si todavía tiene problemas con esto, la respuesta de @ yeahdixon debe ser correcta, y creo que la documentación en sí es correcta. –

+0

Acepto que su metodología funciona, pero creo que la documentación es correcta y podría estar un paso más cerca de "sincrónico". –

5

Miré a los docs en dependencias circulares: http://requirejs.org/docs/api.html#circular

Si hay una dependencia circular con ayb, dice en su módulo que se debe agregar como una dependencia en su módulo l ike modo:

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

entonces cuando se necesita "a" a llamarlo "a" de este modo:

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

Esto funcionó para mí

+0

esto funcionó para mí también –

Cuestiones relacionadas