Comment gérer les dépendances circulaires avec RequireJS / AMD?

Dans mon système, j'ai un certain nombre de «classes» chargées dans le navigateur chaque fois qu'un fichier distinct pendant le développement et concaté pour la production. Lorsqu'ils sont chargés, ils initialisent une propriété sur un objet global, ici G , comme dans cet exemple:

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

Au lieu d'utiliser mon propre objet global, je considère faire de chaque classe son propre module AMD , basé sur la suggestion 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"); }); 

La question est que, avant, il n'y avait pas de déclaration de dépendance entre Employé et Société: vous pourriez mettre la déclaration dans l'ordre que vous vouliez, mais maintenant, en utilisant RequireJS, cela introduit une dépendance, qui est ici (intentionnellement) circulaire, donc la Le code ci-dessus échoue. Bien sûr, dans addEmployee() , ajouter une première ligne var Employee = require("Employee"); Cela permettrait de fonctionner , mais je vois que cette solution est inférieure à ne pas utiliser RequireJS / AMD car elle me demande, le développeur, de prendre conscience de cette dépendance circulaire nouvellement créée et de faire quelque chose à ce sujet.

Existe-t-il un meilleur moyen de résoudre ce problème avec RequireJS / AMD, ou puis-je utiliser RequireJS / AMD pour quelque chose dont il n'a pas été conçu?

    Il s'agit en effet d'une restriction au format AMD. Vous pouvez utiliser les exportations, et ce problème disparaît. Je trouve que les exportations sont fausses, mais c'est la façon dont les modules CommonJS réguliers résolvent le problème:

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

    Sinon, le requérant ("Employé") que vous mentionnez dans votre message fonctionnerait également.

    En général, avec les modules, vous devez être plus conscient des dépendances circulaires, AMD ou non. Même en JavaScript, vous devez être sûr d'utiliser un objet comme l'objet G dans votre exemple.

    Je pense que c'est un inconvénient dans les grands projets où les dépendances circulaires (à plusieurs niveaux) ne sont pas détectées. Cependant, avec madge, vous pouvez imprimer une liste de dépendances circulaires pour les aborder.

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

    Si vous n'avez pas besoin de vos dépendances pour être chargées au début (p. Ex., Lorsque vous étendez une classe), voici ce que vous pouvez faire: (à partir de http://requirejs.org/docs/api.html# Circulaire )

    Dans le fichier a.js :

      define( [ 'B' ], function( B ){ // Just an example return B.extend({ // ... }) }); 

    Et dans l'autre fichier 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(); } }; }); 

    Dans l'exemple de l'OP, c'est comme ça qu'il changerait:

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

    Je voudrais simplement éviter la dépendance circulaire. Peut-être quelque chose comme:

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

    Je ne pense pas que c'est une bonne idée de contourner ce problème et essayer de garder la dépendance circulaire. Se sent comme une mauvaise pratique générale. Dans ce cas, il peut fonctionner car vous avez vraiment besoin de ces modules lorsque la fonction exportée est appelée. Mais imaginez le cas où les modules sont nécessaires et utilisés dans les fonctions de définition réelles elles-mêmes. Aucune solution de rechange ne fera que cela fonctionne. C'est probablement pourquoi require.js échoue rapidement sur la détection de dépendance circulaire dans les dépendances de la fonction de définition.

    Si vous devez vraiment ajouter un travail, l'IMO est plus propre à exiger une dépendance juste à temps (dans vos fonctions exportées dans ce cas), les fonctions de définition fonctionneront bien. Mais l'IMO encore plus propre est juste d'éviter complètement les dépendances circulaires, ce qui est vraiment facile à faire dans votre cas.

    Toutes les réponses affichées (sauf https://stackoverflow.com/a/25170248/14731 ) sont fausses. Même la documentation officielle (en novembre 2014) est fausse.

    La seule solution qui a fonctionné pour moi est de déclarer un fichier "gatekeeper", et de définir toute méthode qui dépend des dépendances circulaires. Voir https://stackoverflow.com/a/26809254/14731 pour un exemple concret.


    Voici pourquoi les solutions ci-dessus ne fonctionneront pas.

    1. Vous ne pouvez pas:
     var a; require(['A'], function( A ){ a = new A(); }); 

    Et ensuite utiliser a plus tard, car il n'y a aucune garantie que ce bloc de code sera exécuté avant le bloc de code qui utilise a . (Cette solution est trompeuse car elle fonctionne 90% du temps)

    1. Je ne vois aucune raison de croire que les exports ne sont pas vulnérables à la même condition de course.

    La solution à ceci est:

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

    Maintenant, nous pouvons utiliser ces modules A et B dans le 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 }); 

    J'ai regardé les documents sur les dépendances circulaires: http://requirejs.org/docs/api.html#circular

    S'il existe une dépendance circulaire avec a et b, il est indiqué dans votre module d'ajouter comme une dépendance dans votre module, de la manière suivante:

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

    Alors, lorsque vous avez besoin d'un "appel", appelez simplement "a" comme suit:

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

    Cela a fonctionné pour moi