Короткий ответ
Вероятно, у вас круговая зависимость, т.е. у вас есть файл a
, который require
s file b
, но файл b
также require
s file a
. В сочетании с использованием module.exports = something
(вместо изменения существующего объекта exports
) это приводит к ситуации, когда один из модулей получит пустой объект, возвращенный из require
другого (кажется, что ни один из его экспортов не существует там).
Быстрое исправление, скорее всего, заменит module.exports = { app, db }
на exports.app = app; exports.db = db
или Object.assign(exports, { app, db })
.
Относительно того, почему, и какие другие варианты существуют в случае, если это не лучшее решение, читайте ниже.
Длинный ответ
Звучит так, как будто у вас проблема с круговой зависимостью.
Позвольте мне объяснить, и терпите меня, мы ' В конце концов я займусь вашей проблемой.
Видите ли, каждый модуль начинается с пустого объекта как module.exports
(также доступен как exports
). Если вы только измените этот объект, выполнив exports.something = something
(или module.exports.something = something
), во многих случаях все может работать даже с циклической зависимостью на верхнем уровне, если свойства объекта экспорта доступ только позже. Например:
/* a.js */
const b = require('./b')
exports.getX = function () {
return b.getY() * 2
}
exports.getZ = function () {
return 5
}
/* b.js */
const a = require('./a')
exports.getY = function () {
return a.getZ() * 3
}
/* main.js */
const a = require('./a')
console.log(a.getX()) // Returns 30
Здесь у нас есть круговая зависимость (a
зависит от b
, но b
также зависит от a
). Тем не менее, он работает.
Приведенный выше код работает, потому что при вычислении a.js
его объект exports
уже будет существовать, а когда он требует b.js
, а b.js
снова требует a.js
, тогда, даже если a.js
еще даже не завершил выполнение своего кода верхнего уровня, переменной a
в b.js
уже может быть присвоен объект exports
из a.js
. На данный момент это пустой объект, но к моменту запуска строки return a.getZ() * 3
у объекта будет установлено свойство getZ
.
main
требуется a
a
требует b
b
требует a
и получает объект, который равен в настоящее время пустой b
определяет getY
при экспорте и возврате a
определяет getX
и getZ
в своем экспорте (который является тем же самым объектом, на который b
уже ссылается!) и возвращает main
звонки a.getX()
a.getX
звонки b.getY
b.getY
только теперь получает доступ к свойству getZ
из a
, которое уже существует (это было установлено на шаге 5).
Обратите внимание, что это не сработало бы, если бы мы написали const { getZ } = require('./a')
, потому что тогда к свойству getZ
был бы доступ уже на шаге 3, когда он не
Но, точно так же, он бы тоже перестал работать, если бы мы написали a.js
вот так:
const b = require('./b')
function getX () {
return b.getY() * 2
}
function getZ () {
return 5
}
module.exports = { getX, getZ }
Большая разница вот это , теперь мы переназначаем объект экспорта ! Итак, объект, который b
получает на шаге 3, является исходным (пустым) объектом, но затем мы переназначаем его новому объекту (вместо изменения существующего объекта), и код в b
никогда не имеет шансов чтобы получить ссылку на этот объект! Тогда у вас будет такая точная ситуация: A require
дает вам пустой объект, и попытка вызвать a.getZ
завершится ошибкой с a.getZ is not a function
(потому что это undefined
).
(Кстати , module.exports = something
это не то же самое, что exports = something
, потому что последний переназначает локальную переменную exports
и не меняет то, что другие модули будут видеть как экспорт из вашего!)
Мое предположение прямо сейчас заключается в том, что у вас здесь аналогичная проблема - что файл, который выполняет соединение с БД, требует файла, который использует соединение с БД, но и наоборот.
Теперь у вас есть два варианта:
- Заменить
module.exports = { app, db }
на exports.app = app; exports.db = db
или Object.assign(exports, { app, db })
- оба варианта изменяют существующий объект экспорта вместо его замены.
Первый вариант, вероятно, самый простой немедленное решение проблемы. Но я не знаю остальной части вашего кода, поэтому могут быть другие препятствия, которые мешают этому решению быть жизнеспособным.
Итак, позвольте мне подробно объяснить второй вариант, потому что он может решить проблему тоже часто.
Пример второго варианта:
/* a.js */
let b
function getX () {
return b.getY() * 2
}
function getZ () {
return 5
}
function init () {
b = require('./b')
}
module.exports = { init, getX, getZ }
/* b.js */
const a = require('./a')
function getY = function () {
return a.getZ() * 3
}
module.exports = { getY }
/* main.js */
const a = require('./a')
a.init()
console.log(a.getX()) // Returns 30
Этот код теперь работает , хотя он переназначает module.exports
, потому что теперь все происходит в другом порядке:
main
требует a
a
заменяет свой объект экспорта на тот, который содержит init
, getX
и getZ
и возвращает [обратите внимание, что эта часть кода ранее выполнялась намного позже] main
вызывает a.init()
a.init
требует b
b
требует a
и получает свой окончательный объект экспорта со всеми методами [обратите внимание, что ранее он получал еще не заполненный объект, потому что a
также еще не был полностью загружен!] b
заменяет его объект экспорта с одним, который содержит getY
и возвращает a.init
возвращает - С этого момента все работает, потому что оба
a
и b
уже имеют ссылки на полностью заполненные объекты экспорта друг друга
Другой способ реализовать второй вариант «требования в более поздний момент времени» - это require
другой модуль внутри экспортированной функции, который уже используется, но это может иметь свои недостатки (например, дублирование кода, если он требуется во многих функциях и немного медленнее, потому что require
приходится вызывать снова и снова). В нашем примере это будет означать следующее:
/* b.js */
function getY () {
const a = require('./a')
return a.getZ() * 3
}
module.exports = { getY }