| Nodejs | circular | exports | module.exports |
Và sai lầm của tuổi trẻ được thể hiện từ quá khứ đến hiện tại và ngược lại, lẫn lộn khó hiểu.
1. Tại sao lại có sai lầm?
- Code nodejs cũng được 1 thời gian tương đối, nhưng mình sử dụng js viết code như java (trước kia đã dùng) nên thực sự mình không hiểu quá nhiều và sâu về js 😓
- Từ những ngày đầu code nodejs, khi mà code vẫn còn non và gà, mình cũng đã dính lỗi require lẫn nhau giữa các file.
- Khi start server và run thử, thì nó bắn ra những lỗi kiểu như
cái gì đó is not a function - Mình ngồi debug mòn đít mà chưa rõ nguyên nhân vì sao. Chỉ biết search gg và tìm cách fix, nhưng chưa rõ ngọn ngành 😔
- Ví dụ đơn giản (không phải trường hợp thực tế)
// file a.js
const b = require('./b');
function getModuleB() {
return b.getModuleA();
}
function getModuleA() {
return "moduleA";
}
module.exports = {
getModuleB: getModuleB,
getModuleA: getModuleA
}
// file b.js
const a = require('./a');
function getModuleA() {
return a.getModuleA();
}
module.exports = {
getModuleA: getModuleA
}
- Chạy code ở file
index.js
// file index.js
const a = require('./a');
console.log(a.getModuleB());
// Lỗi
// TypeError: a.getModuleA is not a function
// at Object.getModuleA (/Users/datpt/Desktop/nodejs/download-file/b.js:6:12)
// ...
- Cùng đi tìm hiểu vì sao đoạn code trên bị lỗi và cách xử lý sẽ làm như nào?
2. Điểm qua lại một vài kiến thức
2.1 exports và module.exports
- Các bạn có thể tìm hiểu rõ hơn về 2 khái niệm này trên trang chủ nodejs
- Hiểu một cách đơn giản khi một file
.jsđược thực thi trên môi trường nodejs, nó sẽ có thêm thành phần làmodule.exportslà 1 thành phần thuộcmodule
var module = { exports: {} };
var exports = module.exports;
// your code
return module.exports;
- Ngoài ra có thêm biến
exportstrỏ cùng vào địa chỉ ô nhớ vớimodule.exportsvà kết thúc file làreturn module.exports;
2.2 Vấn đề là bị làm sao và circular là như nào?
- Như vậy bản chất khi 1 file
.jsđược thực thi nó sẽ return 1 đối tượng làmodule.exports; - Quay lại với ví dụ được nêu ra ở phần 1 chúng ta thấy 1 đặc điểm chung ở 2 file
a.jsvàb.jsđó là đều trỏmodule.exportsvào 1 object mớimodule.exports = {obj mới}, không còn là địa chỉ default. - Khi đoạn code trong
index.jsđược thực thi, filea.jsđược require nên sẽ được gọi, trong filea.jslại requireb.js. Lúc này code tronga.jschưa được thực thi xong nhưng mặc định đã cómodule.exportsđược sinh ra ngay từ đầu. - Trong file
b.jslại require lạia.js.a.jsđã được require trước đó bởiindex.jsnên đoạn code trongasẽ không cần được gọi lại.const a = require('./a')trongbsẽ trỏ vào địa chỉmodule.exportsmặc định củaa. - Sau khi code trong
bđược thực thi xong thì code trong fileamới được thực thi phần còn lại. Nhưng kết thúc fileata lại gánmodule.exports= một object mới, cái mà trước đó trongbghi nhận 1 địa chỉ default củaa😓. - Việc require chéo nhau được gọi là
circular. Do vậy khi đoạn codeconsole.log(a.getModuleB())trongindexđược thực thi ta gọi đến hàm tronga, từagọi qua hàm củab, từblại gọi qua hàm củaa, nhưng lúcbgọi hàm củaadomodule.exportscủa a đã bị thay đổi giá trị default nên trongbsẽ không có những hàm đó =>TypeError: a.getModuleA is not a functionlà ở đây.
3. Giải quyết vấn đề
- Sau khi đã biết rõ nguyên nhân, ta có thể sửa để đoạn code trên có thể thực thi.
- Đơn giản là ta sẽ giữ nguyên địa chỉ mặc định của
module.exports, không gán nó với một giá trị mới. - Nếu đã tìm hiểu về
module.exportsvàexportsta chỉ cần thêm những hàm cần exports vào đối tượng này.
// file a.js
exports.getModuleB = getModuleB
exports.getModuleA = getModuleA
// hoặc
module.exports.getModuleB = getModuleB
module.exports.getModuleA = getModuleA
- Có 2 cách để viết, nhưng vì
module.exportsvàexportslà như nhau trong trường hợp này nên chọn cách viết ngắn hơn.
// file b.js
exports.getModuleA = getModuleA
- Khi chạy lại file
index.jssẽ không còn bị lỗi nữa, nhưng sẽ nhận được warning như này:Warning: Accessing non-existent property 'Symbol(nodejs.util.inspect.custom)' of module exports inside circular dependency. - Node đã cảnh báo cho ta biết về việc các module phụ thuộc chéo nhau. Điều này theo mình là nên tránh.
4. Kết luận gì ở đây!
- Rõ ràng vấn đề
circularnên cần tránh, đoạn code được nêu ra ở phần 1 chỉ là ví dụ đơn giản. - Có thể fix để đoạn code đó không lỗi nhưng cách đó vẫn bị warning về circular.
- Do cách design code ban đầu có vấn đề, dẫ đến các module require lẫn nhau. Ta nên có design rõ ràng. Như trong ví dụ trên nếu để thiết kế lại ta có thể tạo 1 module require cả
avàbvà xử lý riêng, fileindexsẽ require module mới này.