Как обрабатывать циклические зависимости с RequireJS / AMD? - PullRequest
77 голосов
/ 03 февраля 2011

В моей системе у меня есть несколько «классов», загруженных в браузер, каждый из которых имеет отдельные файлы во время разработки, и объединены для производства. По мере загрузки они инициализируют свойство глобального объекта, здесь G, как в этом примере:

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

Вместо того, чтобы использовать свой глобальный объект, я собираюсь сделать каждый класс отдельным модулем AMD , основываясь на предложении Джеймса Бёрка :

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

Проблема в том, что раньше между Employee и Company не было никакой зависимости объявления времени: вы могли бы поместить объявление в любом порядке, который вы хотели, но теперь, используя RequireJS, это вводит зависимость, которая здесь (намеренно) циклическая , поэтому вышеприведенный код не работает. Конечно, в addEmployee() добавление первой строки var Employee = require("Employee"); заставит его работать , но я считаю, что это решение хуже, чем использование RequireJS / AMD, так как это требует от меня, разработчика, быть в курсе этой недавно созданной круговой зависимости и что-то с этим поделать.

Есть ли лучший способ решить эту проблему с помощью RequireJS / AMD, или я использую RequireJS / AMD для чего-то, для чего он не предназначен?

Ответы [ 7 ]

59 голосов
/ 03 февраля 2011

Это действительно ограничение в формате AMD.Вы можете использовать экспорт, и эта проблема исчезнет.Я считаю, что экспорт выглядит некрасиво, но именно так обычные модули CommonJS решают проблему:

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

В противном случае, требование ("Сотрудник"), которое вы упоминаете в своем сообщении, также будет работать.* В целом, с модулями вам нужно больше понимать циклические зависимости, AMD или нет.Даже в обычном JavaScript вы должны быть уверены, что используете в своем примере объект, подобный объекту G.

15 голосов
/ 19 октября 2013

Я думаю, что это является большим недостатком в больших проектах, где (многоуровневые) циклические зависимости остаются незамеченными. Однако с madge вы можете распечатать список циклических зависимостей, чтобы приблизиться к ним.

madge --circular --format amd /path/src
7 голосов
/ 23 октября 2013

Если вам не нужно загружать свои зависимости в начале (например, когда вы расширяете класс), то это то, что вы можете сделать: (взято из http://requirejs.org/docs/api.html#circular)

Вfile a.js:

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

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

    });

И в другом файле 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();
            }
        };

    });

В примере OP это будет следующим образом:

    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");
    });
5 голосов
/ 26 июня 2015

Я посмотрел документы по круговым зависимостям: http://requirejs.org/docs/api.html#circular

Если есть циклическая зависимость с a и b, в вашем модуле говорится, что необходимо добавить require в качестве зависимости в ваш модуль, например:

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

тогда, когда вам нужно "a", просто назовите "a" примерно так:

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

Это сработало для меня

5 голосов
/ 27 ноября 2014

Все опубликованные ответы (кроме https://stackoverflow.com/a/25170248/14731) неверны. Даже официальная документация (по состоянию на ноябрь 2014 г.) неверна.

Единственное решение, которое мне помогло, - это объявить "привратником""file, и пусть он определяет любой метод, который зависит от циклических зависимостей. См. https://stackoverflow.com/a/26809254/14731 для конкретного примера.


Вот почему вышеупомянутые решения не будут работать.

  1. Вы не можете:
var a;
require(['A'], function( A ){
     a = new A();
});

и затем использовать a позже, потому что нет гарантии, что этот блок кода будет выполнен перед блоком кода, который использует a. (Это решение вводит в заблуждение, потому что оно работает 90% времени)

Я не вижу причин полагать, что exports не уязвим к тому же состоянию расы.

решение этого вопроса:

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

    });

теперь мы можем использоватьэти модули A и B в модуле 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

    });
5 голосов
/ 07 августа 2014

Я бы просто избежал круговой зависимости. Может быть что-то вроде:

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

Не думаю, что это хорошая идея, чтобы обойти эту проблему и попытаться сохранить циклическую зависимость. Просто ощущается как общая плохая практика. В этом случае это может работать, потому что вам действительно нужны эти модули для вызова экспортируемой функции. Но представьте себе случай, когда модули требуются и используются в самих функциях определения. Никакой обходной путь не сделает эту работу. Вероятно, поэтому с помощью require.js происходит быстрое обнаружение циклических зависимостей в зависимостях функции определения.

Если вам действительно нужно добавить обходной путь, более чистым IMO является требование зависимости как раз вовремя (в ваших экспортируемых функциях в данном случае), тогда функции определения будут работать нормально. Но даже более чистое IMO - это всего лишь полное исключение циклических зависимостей, что в вашем случае кажется действительно простым.

0 голосов
/ 05 сентября 2018

В моем случае я решил круговую зависимость, переместив код «более простого» объекта в более сложный. Для меня это была коллекция и модельный класс. Я думаю, в вашем случае я бы добавил специфичные для Сотрудника части Компании в класс Сотрудника.

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

Немного хакерский, но он должен работать для простых случаев. И если вы реорганизуете addEmployee, чтобы принять Employee в качестве параметра, зависимость должна быть еще более очевидной для посторонних.

...