Pertanyaan Bagaimana menangani dependensi sirkuler dengan RequireJS / AMD?


Di sistem saya, saya memiliki sejumlah "kelas" yang dimuat di browser masing-masing file terpisah selama pengembangan, dan digabungkan bersama untuk produksi. Ketika mereka dimuat, mereka menginisialisasi properti pada objek global, di sini G, seperti dalam contoh ini:

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

Alih-alih menggunakan objek global saya sendiri, saya mempertimbangkan untuk membuat setiap kelasnya sendiri Modul AMD, berdasarkan Saran 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");
});

Masalahnya adalah bahwa sebelumnya, tidak ada ketergantungan waktu-declare antara Karyawan dan Perusahaan: Anda dapat menempatkan deklarasi dalam urutan apa pun yang Anda inginkan, tetapi sekarang, dengan menggunakan RequireJS, ini memperkenalkan ketergantungan, yang ada di sini (dengan sengaja) melingkar, sehingga kode di atas gagal. Tentu saja, di addEmployee(), menambahkan baris pertama var Employee = require("Employee"); akan buat itu bekerja, tapi saya melihat solusi ini lebih rendah daripada tidak menggunakan RequireJS / AMD karena mengharuskan saya, pengembang, untuk menyadari ketergantungan lingkaran yang baru dibuat ini dan melakukan sesuatu tentang itu.

Apakah ada cara yang lebih baik untuk mengatasi masalah ini dengan RequireJS / AMD, atau apakah saya menggunakan RequireJS / AMD untuk sesuatu yang tidak dirancang untuknya?


75
2018-02-02 23:12


asal


Jawaban:


Ini memang pembatasan dalam format AMD. Anda bisa menggunakan ekspor, dan masalah itu hilang. Saya menganggap ekspor jelek, tetapi bagaimana CommonJS biasa memecahkan masalah:

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

Jika tidak, permintaan ("Karyawan") yang Anda sebutkan dalam pesan Anda juga akan berhasil.

Secara umum dengan modul Anda harus lebih sadar akan ketergantungan melingkar, AMD atau tidak. Bahkan dalam JavaScript biasa, Anda harus yakin untuk menggunakan objek seperti objek G dalam contoh Anda.


59
2018-02-03 00:17



Saya pikir ini cukup merugikan dalam proyek yang lebih besar di mana (multi-level) dependensi melingkar tidak terdeteksi. Namun, dengan madge Anda dapat mencetak daftar dependensi melingkar untuk mendekati mereka.

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

15
2017-10-19 18:10



Jika Anda tidak perlu ketergantungan Anda untuk dimuat di awal (misalnya, ketika Anda memperluas kelas), maka ini adalah apa yang dapat Anda lakukan: (diambil dari http://requirejs.org/docs/api.html#circular)

Di dalam file a.js:

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

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

    });

Dan di file lain 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();
            }
        };

    });

Dalam contoh OP, ini adalah bagaimana hal itu akan berubah:

    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



Saya hanya akan menghindari ketergantungan melingkar. Mungkin sesuatu seperti:

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

Saya tidak berpikir itu ide yang baik untuk mengatasi masalah ini dan mencoba untuk menjaga ketergantungan melingkar. Hanya terasa seperti praktik buruk yang umum. Dalam hal ini dapat bekerja karena Anda benar-benar membutuhkan modul-modul itu ketika fungsi yang diekspor dipanggil. Tetapi bayangkan kasus di mana modul diperlukan dan digunakan dalam fungsi definisi yang sebenarnya itu sendiri. Tidak ada solusi akan membuat pekerjaan itu. Itu mungkin mengapa require.js gagal cepat pada deteksi ketergantungan melingkar dalam dependensi fungsi definisi.

Jika Anda benar-benar harus menambahkan pekerjaan di sekitar, yang lebih bersih IMO adalah membutuhkan ketergantungan tepat pada waktunya (dalam fungsi Anda yang diekspor dalam kasus ini), maka fungsi definisi akan berjalan baik. Tetapi bahkan IMO yang lebih bersih hanya untuk menghindari ketergantungan melingkar sama sekali, yang terasa sangat mudah dilakukan dalam kasus Anda.


5
2017-08-06 21:09



Semua jawaban yang diposting (kecuali https://stackoverflow.com/a/25170248/14731) salah. Bahkan dokumentasi resmi (per November 2014) salah.

Satu-satunya solusi yang berhasil bagi saya adalah mendeklarasikan file "gatekeeper", dan telah mendefinisikan metode apa pun yang bergantung pada dependensi melingkar. Lihat https://stackoverflow.com/a/26809254/14731 untuk contoh konkret.


Inilah mengapa solusi di atas tidak akan berfungsi.

  1. Kamu tidak bisa:
var a;
require(['A'], function( A ){
     a = new A();
});

lalu gunakan a nanti, karena tidak ada jaminan bahwa blok kode ini akan dieksekusi sebelum blok kode yang digunakan a. (Solusi ini menyesatkan karena berfungsi 90% dari waktu)

  1. Saya tidak melihat alasan untuk percaya itu exports tidak rentan terhadap kondisi balapan yang sama.

solusi untuk ini adalah:

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

    });

sekarang kita dapat menggunakan modul A dan B ini dalam modul 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
2017-11-27 20:48



Saya melihat dokumen pada dependensi melingkar:http://requirejs.org/docs/api.html#circular

Jika ada ketergantungan melingkar dengan a dan b, itu mengatakan dalam modul Anda untuk menambahkan memerlukan sebagai ketergantungan dalam modul Anda seperti:

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

maka ketika Anda membutuhkan "a" panggil saja "a" seperti:

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

Ini berhasil bagi saya


5
2018-06-25 21:17