Pregunta

En mi sistema, tengo una serie de "clases" cargadas en el navegador, cada uno de los casos separados durante el desarrollo, y concatenadas para la producción. A medida que se cargan, inicializan una propiedad en un objeto global, aquí G, como en este ejemplo:

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

En lugar de usar mi propio objeto global, estoy considerando hacer que cada clase sea suya Módulo AMD, Residencia en Sugerencia de James 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");
});

El problema es que antes, no había dependencia del tiempo de declaración entre los empleados y la empresa: podría poner la declaración en cualquier orden que quisiera, pero ahora, usando las necesidades, esto introduce una dependencia, que está aquí (intencionalmente) circular, por lo que el El código anterior falla. Por supuesto, en addEmployee(), agregando una primera línea var Employee = require("Employee"); haría hazlo funcionar, pero veo que esta solución es inferior a no usar requerir JS/AMD, ya que me requiere, el desarrollador, para ser consciente de esta dependencia circular recién creada y hacer algo al respecto.

¿Existe una mejor manera de resolver este problema con requestjs/amd, o estoy usando requestjs/amd para algo para lo que no fue diseñado?

¿Fue útil?

Solución

De hecho, esto es una restricción en el formato AMD. Podrías usar exportaciones, y ese problema desaparece. Encuentro que las exportaciones son feas, pero es la forma en que los módulos CommonJS regulares resuelven el problema:

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

De lo contrario, el requerimiento ("empleado") que menciona en su mensaje también funcionaría.

En general, con los módulos, debe ser más consciente de las dependencias circulares, AMD o no. Incluso en JavaScript simple, debe asegurarse de usar un objeto como el objeto G en su ejemplo.

Otros consejos

Creo que este es un gran inconveniente en proyectos más grandes donde las dependencias circulares (multinivel) habitan sin ser detectadas. Sin embargo, con loco Puede imprimir una lista de dependencias circulares para acercarse a ellas.

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

Si no necesita que se carguen sus dependencias al principio (por ejemplo, cuando extiende una clase), entonces esto es lo que puede hacer: (tomado de http://requirejs.org/docs/api.html#circular)

En el archivo a.js:

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

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

    });

Y en el otro archivo 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();
            }
        };

    });

En el ejemplo del OP, así es como cambiaría:

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

Solo evitaría la dependencia circular. Quizás algo como:

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

No creo que sea una buena idea resolver este problema e intentar mantener la dependencia circular. Simplemente se siente como una mala práctica general. En este caso, puede funcionar porque realmente requiere esos módulos para cuando se llama a la función exportada. Pero imagine el caso en el que se requieren y se usan los módulos en las funciones de definición real. Ninguna solución hará que eso funcione. Probablemente sea por eso requerir.Js falla rápidamente en la detección de dependencia circular en las dependencias de la función de definición.

Si realmente tiene que agregar un trabajo, el limpiador One IMO es requerir una dependencia justo a tiempo (en sus funciones exportadas en este caso), entonces las funciones de definición funcionarán bien. Pero aún más limpio, la OMI es solo para evitar dependencias circulares por completo, lo que se siente realmente fácil de hacer en su caso.

Todas las respuestas publicadas (excepto https://stackoverflow.com/a/25170248/14731) estan equivocados. Incluso la documentación oficial (a partir de noviembre de 2014) es incorrecta.

La única solución que funcionó para mí es declarar un archivo "Gatekeeper" y hacer que defina cualquier método que dependa de las dependencias circulares. Ver https://stackoverflow.com/a/26809254/14731 para un ejemplo concreto.


He aquí por qué las soluciones anteriores no funcionarán.

  1. No puedes:
var a;
require(['A'], function( A ){
     a = new A();
});

y luego usar a Más adelante, porque no hay garantía de que este bloque de código se ejecute antes del bloque de código que usa a. (Esta solución es engañosa porque funciona el 90% del tiempo)

  1. No veo ninguna razón para creer que exports no es vulnerable a la misma condición de carrera.

La solución a esto es:

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

    });

Ahora podemos usar estos módulos A y B en el Módulo 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

    });

Miré los documentos en dependencias circulares:http://requirejs.org/docs/api.html#circular

Si hay una dependencia circular con A y B, dice en su módulo para agregar una dependencia en su módulo como así:

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

Luego, cuando necesite "un" solo llame "a" como así:

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

Esto funcionó para mi

En mi caso, resolví la dependencia circular moviendo el código del objeto "más simple" al más complejo. Para mí eso fue una colección y una clase modelo. Supongo que en su caso agregaría las partes específicas de los empleados de la empresa a la clase de empleados.

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

Un poco hacky, pero debería funcionar para casos simples. Y si te refactor addEmployee Para tomar a un empleado como parámetro, la dependencia debería ser aún más obvia para los extraños.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top