Question

Dans mon système, j'ai un certain nombre de "classes" chargées dans le navigateur, chacune des fichiers séparés pendant le développement, et concaténées ensemble pour la production. Au fur et à mesure de leur chargement, 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, j'envisage de faire de chaque classe son propre module AMD , basé sur James Suggestion de 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 au moment de la déclaration entre l'employé et l'entreprise: 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(), l'ajout d'une première ligne var Employee = require("Employee"); le fera fonctionner , mais je Je considère cette solution comme inférieure à ne pas utiliser RequireJS / AMD car elle m'oblige, le développeur, à être conscient de cette dépendance circulaire nouvellement créée et à faire quelque chose à ce sujet.

Existe-t-il un meilleur moyen 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?

Était-ce utile?

La solution

Il s'agit en effet d'une restriction au format AMD.Vous pourriez utiliser les exportations, et ce problème disparaît.Je trouve que les exportations sont laides, mais c'est ainsi que 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 require ("Employee") 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 brut, vous devez vous assurer d'utiliser un objet comme l'objet G dans votre exemple.

Autres conseils

Je pense que c'est tout à fait un inconvénient dans les projets plus importants où les dépendances circulaires (à plusieurs niveaux) restent non 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 que vos dépendances soient chargées au début (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 du PO, 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");
    });

J'éviterais simplement 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 ce soit une bonne idée de contourner ce problème et d'essayer de conserver la dépendance circulaire.Ressemble juste à une mauvaise pratique générale.Dans ce cas, cela peut fonctionner car vous avez vraiment besoin de ces modules lorsque la fonction exportée est appelée.Mais imaginez le cas où des modules sont requis et utilisés dans les fonctions de définition proprement dites.Aucune solution de contournement ne fera que cela fonctionne.C'est probablement pourquoi require.js échoue rapidement sur la détection des dépendances circulaires dans les dépendances de la fonction de définition.

Si vous devez vraiment ajouter une solution de contournement, le plus propre IMO doit exiger une dépendance juste à temps (dans vos fonctions exportées dans ce cas), alors les fonctions de définition fonctionneront correctement.Mais une OMI encore plus propre consiste simplement à éviter complètement les dépendances circulaires, ce qui semble vraiment facile à faire dans votre cas.

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.

  1. You cannot:
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 uses a. (This solution is misleading because it works 90% of the time)

  1. I see no reason to believe that 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

    });

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

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.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top