Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle circular dependencies with RequireJS/AMD?

In my system, I have a number of "classes" loaded in the browser each a separate files during development, and concatenated together for production. As they are loaded, they initialize a property on a global object, here G, as in this example:

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

Instead of using my own global object, I am considering to make each class its own AMD module, based on 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"); }); 

The issue is that before, there was no declare-time dependency between Employee and Company: you could put the declaration in whatever order you wanted, but now, using RequireJS, this introduces a dependency, which is here (intentionally) circular, so the above code fails. Of course, in addEmployee(), adding a first line var Employee = require("Employee"); would make it work, but I see this solution as inferior to not using RequireJS/AMD as it requires me, the developer, to be aware of this newly created circular dependency and do something about it.

Is there a better way to solve this problem with RequireJS/AMD, or am I using RequireJS/AMD for something it was not designed for?

like image 922
avernet Avatar asked Feb 02 '11 23:02

avernet


People also ask

How do you resolve circular dependency issues?

There are a couple of options to get rid of circular dependencies. For a longer chain, A -> B -> C -> D -> A , if one of the references is removed (for instance, the D -> A reference), the cyclic reference pattern is broken, as well. For simpler patterns, such as A -> B -> A , refactoring may be necessary.

How do I get around circular dependency?

Circular dependencies can be introduced when implementing callback functionality. This can be avoided by applying design patterns like the observer pattern.

How does node handle circular dependencies?

A circular dependency between Node. js modules occurs when the modules require() each other. This dependency results in an incomplete module being loaded, and mysterious problems later. Using Madge is an excellent way to detect the dependencies.

What is AMD RequireJS?

Advertisements. A module in RequireJS is a scoped object and is not available in the global namespace. Hence, global namespace will not be polluted. RequireJS syntax allows to load modules faster without worrying about keeping track of the order of dependencies.


2 Answers

This is indeed a restriction in the AMD format. You could use exports, and that problem goes away. I find exports to be ugly, but it is how regular CommonJS modules solve the problem:

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

Otherwise, the require("Employee") you mention in your message would work too.

In general with modules you need to be more aware of circular dependencies, AMD or not. Even in plain JavaScript, you have to be sure to use an object like the G object in your example.

like image 59
jrburke Avatar answered Sep 26 '22 19:09

jrburke


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.

madge --circular --format amd /path/src 
like image 41
Pascalius Avatar answered Sep 24 '22 19:09

Pascalius