tutorial define javascript commonjs requirejs

javascript - define - ¿Cómo manejar las dependencias circulares con RequireJS/AMD?



require js tutorial (6)

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

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

En mi sistema, tengo varias "clases" cargadas en el navegador, cada una en un archivo separado durante el desarrollo, y concatenadas juntas para producción. A medida que se cargan, inicializan una propiedad en un objeto global, aquí G , como en este ejemplo:

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 usar mi propio objeto global, estoy considerando hacer de cada clase su propio módulo de AMD , basado en la sugerencia de James Burke :

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

El problema es que antes no existía una dependencia del tiempo de declaración entre el empleado y la empresa: podía poner la declaración en el orden que quisiera, pero ahora, usando RequireJS, esto introduce una dependencia, que 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"); lo haría funcionar , pero veo esta solución como inferior a no usar RequireJS / AMD, ya que me exige a mí, el desarrollador, estar al tanto de esta dependencia circular recién creada y hacer algo al respecto.

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


Esto es de hecho una restricción en el formato AMD. Puede usar exportaciones, y ese problema desaparece. Encuentro que las exportaciones son feas, pero es la forma en que los módulos comunes de 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, el requerimiento ("Empleado") que mencione en su mensaje también funcionaría.

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.


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

Si existe una dependencia circular con a y b, en su módulo se especifica que se requiera como dependencia en su módulo de la siguiente manera:

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

luego, cuando necesites "a" simplemente llama a "a" como tal:

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

Esto funcionó para mí


Si no necesita que sus dependencias se carguen al inicio (por ejemplo, cuando amplíe 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 del OP, así es como cambiaría:

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


Solo evitaría la dependencia circular. Tal vez algo así 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 sea una buena idea evitar este problema e intentar 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 agregar una solución alternativa, la IMO más limpia requiere una dependencia justo a tiempo (en este caso, en las funciones exportadas), las funciones de definición se ejecutará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.


Todas las respuestas publicadas (excepto https://.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. Consulte https://.com/a/26809254/14731 para obtener un ejemplo concreto.

Esta es la razón por la que las soluciones anteriores no funcionarán.

  1. No puedes:

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

y luego use a más adelante, porque no hay garantía de que este bloque de código se ejecutará antes del bloque de código que usa a . (Esta solución es engañosa porque funciona el 90% del tiempo)

  1. No veo ninguna razón para creer que las exports no sean vulnerables a la misma condición racial.

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