Как обрабатывать циклические зависимости с RequireJS / AMD?



в моей системе у меня есть несколько "классов", загруженных в браузер каждый отдельный файл во время разработки и объединенных вместе для производства. При загрузке они инициализируют свойство глобального объекта, здесь 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");
});


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



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

463   7  

7 ответов:

Это действительно ограничение в формате 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 в вашем образец.

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

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

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

в файле 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");
    });

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

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

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

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

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

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


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

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

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

  1. я не вижу причин верить этому 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

    });

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

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

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

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

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

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

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

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

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

Comments

    Ничего не найдено.