it-swarm.com.de

Wie gehe ich mit Zirkularabhängigkeiten mit RequireJS/AMD um?

In meinem System habe ich eine Reihe von "Klassen", die während der Entwicklung jeweils eine separate Datei in den Browser geladen und für die Produktion miteinander verkettet wurden. Beim Laden initialisieren sie eine Eigenschaft für ein globales Objekt, hier G, wie in diesem Beispiel:

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

Anstatt mein eigenes globales Objekt zu verwenden, überlege ich mir, jede Klasse zu einem eigenen AMD-Modul zu machen, basierend auf James Burkes Vorschlag :

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

Das Problem ist, dass zuvor keine Abhängigkeitszeit zwischen Mitarbeiter und Unternehmen bestand: Sie können die Erklärung in beliebiger Reihenfolge eingeben, aber jetzt wird mit RequireJS eine Abhängigkeit eingeführt, die hier (absichtlich) zirkulär ist Der obige Code schlägt fehl. In addEmployee() würde natürlich das Hinzufügen einer ersten Zeile var Employee = require("Employee");damit es funktioniert , aber ich halte diese Lösung für unterlegen, RequireJS/AMD nicht zu verwenden, da ich, der Entwickler, von diesem neu erstellten Programm Kenntnis haben muss kreisförmige Abhängigkeit und etwas dagegen tun.

Gibt es eine bessere Möglichkeit, dieses Problem mit RequireJS/AMD zu lösen, oder verwende ich RequireJS/AMD für etwas, für das es nicht entwickelt wurde?

76
avernet

Dies ist in der Tat eine Einschränkung im AMD-Format. Sie könnten Exporte verwenden, und das Problem wird behoben. Ich finde Exporte hässlich, aber reguläre CommonJS-Module lösen das 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;
});

Andernfalls würde auch die Anforderung ("Mitarbeiter"), die Sie in Ihrer Nachricht angeben, funktionieren.

Im Allgemeinen müssen Sie bei Modulen mehr auf zirkulare Abhängigkeiten achten, auf AMD oder nicht. Selbst in reinem JavaScript müssen Sie sicher sein, dass Sie in Ihrem Beispiel ein Objekt wie das G-Objekt verwenden.

59
jrburke

Ich denke, das ist ein ziemlicher Nachteil in größeren Projekten, in denen (mehrstufige) zirkuläre Abhängigkeiten unentdeckt bleiben . Mit madge können Sie jedoch eine Liste zirkularer Abhängigkeiten drucken, um sie anzugreifen.

madge --circular --format AMD /path/src
15
Pascalius

Wenn Sie nicht benötigen, dass Ihre Abhängigkeiten beim Start geladen werden (z. B. wenn Sie eine Klasse erweitern), können Sie Folgendes tun: (entnommen aus http://requirejs.org/docs/api. HTML # Rundschreiben )

In der Datei a.js:

    define( [ 'B' ], function( B ){

        // Just an example
        return B.extend({
            // ...
        })

    });

Und in der anderen Datei 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();
            }
        };

    });

Im OP-Beispiel würde sich dies so ändern:

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

Ich würde die zirkuläre Abhängigkeit einfach vermeiden. Vielleicht so etwas wie:

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

Ich denke nicht, dass es eine gute Idee ist, dieses Problem zu umgehen und die zirkuläre Abhängigkeit zu bewahren. Fühlt sich nur wie eine allgemeine schlechte Praxis an. In diesem Fall kann es funktionieren, weil Sie diese Module wirklich benötigen, wenn die exportierte Funktion aufgerufen wird. Stellen Sie sich jedoch den Fall vor, in dem Module benötigt und in den eigentlichen Definitionsfunktionen selbst verwendet werden. Keine Problemumgehung macht das möglich. Dies ist wahrscheinlich der Grund, warum Requires.js bei der zyklischen Abhängigkeitserkennung in den Abhängigkeiten der Definitionsfunktion schnell versagt.

Wenn Sie wirklich eine Problemumgehung hinzufügen müssen, muss das Bereinigungs-IMO gerade noch rechtzeitig eine Abhängigkeit erfordern (in diesem Fall in Ihre exportierten Funktionen), dann werden die Definitionsfunktionen einwandfrei ausgeführt. Aber noch sauberere IMO ist nur, um zirkuläre Abhängigkeiten zu vermeiden, was sich in Ihrem Fall wirklich leicht anfühlt.

5
Shu

Ich habe mir die Unterlagen zu zirkularen Abhängigkeiten angesehen: http://requirejs.org/docs/api.html#circular

Wenn es eine zirkuläre Abhängigkeit mit a und b gibt, heißt es in Ihrem Modul, dass Sie als Abhängigkeit in Ihrem Modul Folgendes hinzufügen müssen:

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

dann, wenn Sie "ein" brauchen, rufen Sie einfach "a" an:

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

Das hat bei mir funktioniert

5
yeahdixon

Alle veröffentlichten Antworten (außer https://stackoverflow.com/a/25170248/14731 ) sind falsch. Sogar die offizielle Dokumentation (Stand November 2014) ist falsch.

Die einzige Lösung, die für mich funktioniert hat, besteht darin, eine "Gatekeeper" -Datei zu deklarieren und jede Methode definieren zu lassen, die von den zirkularen Abhängigkeiten abhängt. Ein konkretes Beispiel finden Sie unter https://stackoverflow.com/a/26809254/14731 .


Deshalb funktionieren die oben genannten Lösungen nicht.

  1. Du kannst nicht:
var a;
require(['A'], function( A ){
     a = new A();
});

und verwenden Sie später a, da es keine Garantie gibt, dass dieser Codeblock vor dem Codeblock ausgeführt wird, der a verwendet. (Diese Lösung ist irreführend, da sie in 90% der Fälle funktioniert)

  1. Ich sehe keinen Grund zu der Annahme, dass exports nicht anfällig für die gleiche Rasse ist.

die Lösung dafür ist:

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

    });

jetzt können wir diese Module A und B in Modul C verwenden

//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

    });
5
Gili

In meinem Fall löste ich die zirkuläre Abhängigkeit, indem ich den Code des "einfacheren" Objekts in das komplexere verschiebe. Für mich war das eine Kollektion und eine Modellklasse. Ich denke, in Ihrem Fall würde ich die Employee-spezifischen Unternehmensteile in die Employee-Klasse aufnehmen.

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

Ein bisschen hackig, aber es sollte in einfachen Fällen funktionieren. Und wenn Sie addEmployee umgestalten, um einen Mitarbeiter als Parameter zu übernehmen, sollte die Abhängigkeit für Außenstehende noch offensichtlicher sein.

0
Björn Tantau