Frage Wie behandelt man zirkuläre Abhängigkeiten mit RequireJS / AMD?


In meinem System habe ich eine Reihe von "Klassen" im Browser geladen, die jeweils eine separate Dateien während der Entwicklung und für die Produktion zusammen verkettet. Wenn sie geladen werden, initialisieren sie hier eine Eigenschaft für ein globales Objekt 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, jede Klasse zu ihrer eigenen zu machen AMD-Modul, beyogen 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 es vorher keine Deklarationszeitabhängigkeit zwischen Employee und Company gab: Sie könnten die Deklaration in die von Ihnen gewünschte Reihenfolge bringen, aber jetzt, mit RequireJS, führt dies eine Abhängigkeit ein, die hier (absichtlich) zirkulär ist, also Der obige Code schlägt fehl. Natürlich, in addEmployee(), Hinzufügen einer ersten Zeile var Employee = require("Employee"); würde Bring es zum Laufen, aber ich sehe diese Lösung als unterlegen, RequireJS / AMD nicht zu verwenden, da es mich, den Entwickler, erfordert, sich dieser neu geschaffenen zirkulären Abhängigkeit bewusst zu sein und etwas dagegen zu tun.

Gibt es einen besseren Weg, um dieses Problem mit RequireJS / AMD zu lösen, oder verwende ich RequireJS / AMD für etwas, für das es nicht entwickelt wurde?


75
2018-02-02 23:12


Ursprung


Antworten:


Dies ist in der Tat eine Einschränkung im AMD-Format. Sie könnten Exporte verwenden, und dieses Problem verschwindet. Ich finde, dass Exporte hässlich sind, 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;
});

Anderenfalls würde die Anforderung ("Mitarbeiter"), die Sie in Ihrer Nachricht erwähnen, auch funktionieren.

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


59
2018-02-03 00:17



Ich denke, das ist ein ziemlicher Nachteil in größeren Projekten, wo (mehrstufige) zirkuläre Abhängigkeiten unerkannt bleiben. Jedoch mit madge Sie können eine Liste von zirkulären Abhängigkeiten drucken, um sie zu erreichen.

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

15
2017-10-19 18:10



Wenn Sie nicht möchten, dass Ihre Abhängigkeiten am Anfang geladen werden (z. B. wenn Sie eine Klasse erweitern), können Sie Folgendes tun: (aus http://requirejs.org/docs/api.html#circular)

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 Beispiel des OP würde sich dies wie folgt ä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
2017-10-22 23:46



Ich würde nur die zirkuläre Abhängigkeit vermeiden. Vielleicht 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 zu versuchen, die zirkuläre Abhängigkeit beizubehalten. Fühlt sich einfach wie allgemeine schlechte Praxis an. In diesem Fall kann es funktionieren, weil Sie diese Module wirklich benötigen, wenn die exportierte Funktion aufgerufen wird. Aber stellen Sie sich den Fall vor, in dem Module in den eigentlichen Definitionsfunktionen benötigt und verwendet werden. Keine Problemumgehung wird das funktionieren lassen. Das ist wahrscheinlich der Grund, warum require.js bei der zyklischen Abhängigkeitserkennung in den Abhängigkeiten der Definitionsfunktion schnell ausfällt.

Wenn Sie wirklich eine Arbeit hinzufügen müssen, die sauberere IMO eine Abhängigkeit gerade rechtzeitig (in Ihren exportierten Funktionen in diesem Fall) erfordert, dann werden die Definitionsfunktionen gut ausgeführt. Aber noch sauberer IMO ist nur um zirkuläre Abhängigkeiten zu vermeiden, was sich in Ihrem Fall sehr einfach anfühlt.


5
2017-08-06 21:09



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

Die einzige Lösung, die für mich funktionierte, ist es, eine "Gatekeeper" -Datei zu deklarieren und jede Methode zu definieren, die von den zirkulären Abhängigkeiten abhängt. Sehen https://stackoverflow.com/a/26809254/14731 für ein konkretes Beispiel.


Hier ist warum die obigen Lösungen nicht funktionieren.

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

und dann benutzen a später, weil es keine Garantie dafür gibt, dass dieser Codeblock vor dem verwendeten Codeblock ausgeführt wird a. (Diese Lösung ist irreführend, da sie 90% der Zeit funktioniert)

  1. Ich sehe keinen Grund, das zu glauben exports ist nicht anfällig für die gleiche Race-Bedingung.

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
2017-11-27 20:48



Ich habe mir die Dokumente zu zirkulären Abhängigkeiten angeschaut: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, wie folgt eine Abhängigkeit in Ihrem Modul hinzuzufügen:

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

Wenn du dann "a" brauchst, ruf einfach "a" an:

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

Das hat für mich funktioniert


5
2018-06-25 21:17