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
.exports
là 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
exports
trỏ cùng vào địa chỉ ô nhớ vớimodule.exports
và 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.js
vàb.js
đó là đều trỏmodule.exports
và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.js
lại requireb.js
. Lúc này code tronga.js
chưa được thực thi xong nhưng mặc định đã cómodule.exports
được sinh ra ngay từ đầu. - Trong file
b.js
lại require lạia.js
.a.js
đã được require trước đó bởiindex.js
nên đoạn code tronga
sẽ không cần được gọi lại.const a = require('./a')
trongb
sẽ trỏ vào địa chỉmodule.exports
mặc định củaa
. - Sau khi code trong
b
được thực thi xong thì code trong filea
mới được thực thi phần còn lại. Nhưng kết thúc filea
ta lại gánmodule.exports= một object mới
, cái mà trước đó trongb
ghi 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ừa
gọi qua hàm củab
, từb
lại gọi qua hàm củaa
, nhưng lúcb
gọi hàm củaa
domodule.exports
của a đã bị thay đổi giá trị default nên trongb
sẽ không có những hàm đó =>TypeError: a.getModuleA is not a function
là ở đâ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.exports
vàexports
ta 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.exports
vàexports
là 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.js
sẽ 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 đề
circular
nê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ả
a
vàb
và xử lý riêng, fileindex
sẽ require module mới này.