En mi sistema, tengo una serie de "clases" cargadas en el navegador, cada una de ellas en archivos separados durante el desarrollo, y concatenadas para la 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 que cada clase sea su propio módulo AMD , según 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 había dependencia de tiempo de declaración entre el Empleado y la Compañía: podía colocar 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 falla el código anterior. Por supuesto, addEmployee()
agregar 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 requiere que yo, el desarrollador, esté al tanto de esta dependencia circular recién creada y haga algo al respecto.
¿Existe una mejor manera de resolver este problema con RequireJS / AMD, o estoy usando RequireJS / AMD para algo para lo que no fue diseñado?
fuente
function(exports, Company)
andfunction(exports, Employee)
. Anyway, thanks for RequireJS, it's awsome.I think this is quite a drawback in larger projects where (multi-level) circular dependencies dwell undetected. However, with madge you can print a list of circular dependencies to approach them.
fuente
If you don't need your dependencies to be loaded at the start (e.g., when you are extending a class), then this is what you can do: (taken from http://requirejs.org/docs/api.html#circular)
In the file
a.js
:define( [ 'B' ], function( B ){ // Just an example return B.extend({ // ... }) });
And in the other 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(); } }; });
In the OP's example, this is how it would change:
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"); });
fuente
I looked at the docs on circular dependencies :http://requirejs.org/docs/api.html#circular
If there is a circular dependency with a and b , it says in your module to add require as a dependency in your module like so :
define(["require", "a"],function(require, a) { ....
then when you need "a" just call "a" like so:
return function(title) { return require("a").doSomething(); }
This worked for me
fuente
I would just avoid the circular dependency. Maybe something like:
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);
I don't think it's a good idea to work around this issue and try to keep the circular dependency. Just feels like general bad practice. In this case it can work because you really require those modules for when the exported function is called. But imagine the case where modules are required and used in the actual definition functions itself. No workaround will make that work. That's probably why require.js fails fast on circular dependency detection in the dependencies of the definition function.
If you really have to add a work around, the cleaner one IMO is to require a dependency just in time (in your exported functions in this case), then the definition functions will run fine. But even cleaner IMO is just to avoid circular dependencies altogether, which feels really easy to do in your case.
fuente
All the posted answers (except https://stackoverflow.com/a/25170248/14731) are wrong. Even the official documentation (as of November 2014) is wrong.
The only solution that worked for me is to declare a "gatekeeper" file, and have it define any method that depends on the circular dependencies. See https://stackoverflow.com/a/26809254/14731 for a concrete example.
Here is why the above solutions will not work.
var a; require(['A'], function( A ){ a = new A(); });
and then use
a
later on, because there is no guarantee that this code block will get executed before the code block that usesa
. (This solution is misleading because it works 90% of the time)exports
is not vulnerable to the same race condition.the solution to this is:
//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); } });
now we can use these modules A and B in 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 });
fuente
In my case I solved the circular dependency by moving the code of the "simpler" object into the more complex one. For me that was a collection and a model class. I guess in your case I would add the Employee-specific parts of Company into the Employee class.
define("Employee", ["Company"], function(Company) { function Employee (name) { this.name = name; this.company = new Company(name + "'s own company"); }; Company.prototype.addEmployee = function(name) { var employee = new Employee(name); this.employees.push(employee); employee.company = this; }; return Employee; }); define("Company", [], function() { function Company(name) { this.name = name; this.employees = []; }; return Company; }); define("main", ["Employee", "Company"], function (Employee, Company) { var john = new Employee("John"); var bigCorp = new Company("Big Corp"); bigCorp.addEmployee("Mary"); });
A bit hacky, but it should work for simple cases. And if you refactor
addEmployee
to take an Employee as parameter, the dependency should be even more obvious to outsiders.fuente