80 votes

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, chacune dans un fichier séparé pendant le développement, et concaténées ensemble pour la production. Lorsqu'elles sont chargées, elles 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, j'envisage de faire en sorte que chaque classe ait sa propre Module AMD sur la base de 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");
});

Le problème est qu'avant, il n'y avait pas de dépendance de temps de déclaration entre Employee et Company : vous pouviez 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 le code ci-dessus échoue. Bien sûr, dans addEmployee() en ajoutant une première ligne var Employee = require("Employee"); serait faire en sorte que cela fonctionne Mais je considère cette solution comme inférieure à la non-utilisation de RequireJS/AMD, car elle exige de moi, le développeur, d'être conscient de cette dépendance circulaire nouvellement créée et de faire quelque chose à ce sujet.

Y a-t-il une meilleure façon de résoudre ce problème avec RequireJS/AMD, ou est-ce que j'utilise RequireJS/AMD pour quelque chose pour lequel il n'a pas été conçu ?

59voto

jrburke Points 5361

Il s'agit en effet d'une restriction dans le format AMD. Vous pourriez utiliser les exportations, et ce problème disparaîtrait. Je trouve les exportations laides, mais c'est ainsi que les modules CommonJS ordinaires 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 require("Employee") que vous mentionnez dans votre message fonctionnerait aussi.

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

3 votes

Je pensais qu'il fallait déclarer les exportations dans la liste d'arguments des deux callbacks, par exemple function(exports, Company) y function(exports, Employee) . En tout cas, merci pour RequireJS, c'est génial.

0 votes

@jrburke Je pense que cela peut être fait de manière unidirectionnelle, n'est-ce pas, pour un médiateur ou un noyau ou un autre composant descendant ? Est-ce une mauvaise idée de le rendre accessible par les deux méthodes ? stackoverflow.com/questions/11264827/

1 votes

Je ne suis pas sûr de comprendre comment cela résout le problème. Je crois comprendre que toutes les dépendances doivent être chargées avant l'exécution de la définition. N'est-ce pas le cas si "exports" est passé comme première dépendance ?

15voto

Pascalius Points 1202

Je pense que c'est un inconvénient dans les grands projets où les dépendances circulaires (à plusieurs niveaux) passent inaperçues. Cependant, avec madge vous pouvez imprimer une liste des dépendances circulaires pour les approcher.

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

0 votes

CACSVML-13295:sc-admin-ui-express amills001c$ madge --circular --format amd ./ Aucune dépendance circulaire trouvée !

8voto

redolent Points 1269

Si vous n'avez pas besoin que vos dépendances soient chargées dès le départ (par exemple, lorsque vous étendez une classe), voici ce que vous pouvez faire : (extrait de http://requirejs.org/docs/api.html#circular )

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, voici comment cela 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");
    });

2 votes

Comme Gili l'a dit dans son commentaire, cette solution est erronée et ne fonctionnera pas toujours. Il y a une condition de course sur le bloc de code qui sera exécuté en premier.

7voto

yeahdixon Points 1713

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

S'il y a une dépendance circulaire avec a et b , il est dit dans votre module d'ajouter require comme une dépendance dans votre module comme ceci :

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

puis quand vous avez besoin de "a", appelez "a" comme ça :

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

Cela a fonctionné pour moi

5voto

Gili Points 14674

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

La seule solution qui a fonctionné pour moi est de déclarer un fichier "gatekeeper", et de lui faire 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, parce qu'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 dans 90 % des cas).

  1. Je ne vois aucune raison de croire que exports n'est pas vulnérable à la même condition de course.

la solution à ce problème 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

    });

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X