1-
Имеет ли смысл предварительно загружать некоторые данные во время запуска Express.js (например, чтение закрытых ключей из файла), чтобы они уже находились в памяти, когда это требуется по запросу, и каждый раз использовались бы повторно без повторного использования? читать из файла? В среде PHP-сервера это не имеет большого значения, так как все строится из 0 с каждым запросом.
Да, полностью. Вы запускаете соединения с базами данных, считываете данные для файлов и выполняете аналогичные задачи при запуске приложения, чтобы они всегда были доступны при каждом запросе.
В этом сценарии необходимо учесть несколько моментов:
Во время запуска приложения вы можете безопасно вызывать синхронные методы, такие как fs.readFileSync
и т. Д., Поскольку в данный момент нет единого запроса для одного потока.
Модули CommonJS кэшируют свои первые экспортированные значения. Поэтому, если вы решите использовать специальный модуль для обработки секретов, считанных из файла, соединений с базой данных и т. Д., Вы можете:
secrets.js
const fs = require('fs');
const gmailSecretApiKey = fs.readFileSync('path_to_file');
const mailgunSecretApiKey = fs.readFileSync('path_to_file');
...
module.exports = {
gmailSecretApiKey,
mailgunSecretApiKey,
...
}
Тогда require
это как запуск вашего приложения. После этого любые модули, которые делают:
const gmailKey = require('.../secrets').gmailSecretApiKey
не будет читать из файла снова. Результаты кешируются в модуле.
Это важно, потому что позволяет вам использовать require
и import
для использования конфигурации в ваших контроллерах и модулях, не беспокоясь о передаче дополнительных параметров вашим http-контроллерам или добавлении их в req
объекты.
- В зависимости от инфраструктуры вы не сможете разрешить приложению не обрабатывать запросы во время запуска (т. Е. У вас есть только одна машина, и вы не хотите давать
service unavailble
своим клиентам). В таких случаях вы можете выставлять все настройки и общие ресурсы в обещаниях и загружать свои веб-контроллеры как можно быстрее , ожидая получения обещаний внутри. Допустим, нам нужно, чтобы kafka был запущен и запущен при обработке запроса к / user:
kafka.js
function kafka() {
// return some promise of an object that can publish and read from kafka in a given port etc. etc.
}
module.exports = kafka();
Так что теперь в:
userController.js
const kafka = require('.../kafka');
router.get('/user', (req,res) => {
kafka.then(k => {
k.publish(req.user, 'userTopic'); // or whatever. This is just an example.
});
})
Таким образом, если пользователь отправляет запрос во время начальной загрузки, запрос все равно будет обработан (но это займет некоторое время). Запросы, сделанные, когда обещание уже выполнено, ничего не заметят.
- Нет такой вещи, как несколько потоков в узле. Все, что вы объявляете в модуле commonJS или пишете в
process
, будет доступно в каждом запросе.
2-
Как правильно обрабатывать исключения в серверном процессе Node.js? Если скрипт сервера PHP генерирует фатальное исключение только в том случае, если конкретный запрос умирает, все остальные запросы и любые новые выполняются нормально. Если на сервере Node.js происходит неустранимая ошибка, похоже, что это убьет весь процесс и, следовательно, все запросы к нему.
Это действительно зависит от того, какое исключение вы найдете. Это конкретно связано с обрабатываемым запросом или что-то критичное для всего приложения?
В первом случае вы хотите перехватить исключение и не позволить всему потоку умереть. Теперь «поймать исключение» в javascript сложно , потому что вы не можете catch
асинхронные исключения / ошибки, и вы, вероятно, будете использовать process.on('unhandledRejection')
для обработки этого, например:
// main.js
try {
bootstrapMongoDb();
bootstrapKafka();
bootstrapSecrets();
... wahtever
bootstrapExpress();
} catch(e){
// read what `e` brings and decide.
// however, is worth to mention that errors raised during handling
// http request won't ever get handled here, because they are
// asynchronous. try/catch in javascript don't catch asynchronous errors.
}
process.on('unhandledRejection', e => {
// now here we are treating unhandled promise rejections, and errors that raise
// in express controllers are likely end up here. of course, I'm talking about
// promise rejections. I am not sure if this can catch Errors thrown in callbacks.
// You should never `throw new Error` inside an asynchronous callback.
});
Обработка ошибок в приложении узла - это отдельная тема, слишком широкая, чтобы рассматривать ее здесь. Однако некоторые советы не должны причинять вреда:
Никогда не выбрасывайте ошибки в обратных вызовах. throw
синхронно. Обратные вызовы и асинхронность должны основываться на параметре error
или отклонении обещания.
Тебе лучше привыкнуть к обещаниям. Обещания действительно улучшают управление ошибками в асинхронном коде.
Ошибки Javascript могут быть украшены дополнительными полями, так что вы можете заполнить идентификаторы трассировки и другие идентификаторы, которые могут быть полезны при чтении журналов вашей системы, если вы будете регистрировать ваши необработанные ошибки.
Теперь, в последнем случае ... иногда случаются сбои, которые совершенно губительны для вашего приложения.Возможно, вам совершенно необходимо подключение к серверу kafka или mongo, и если оно сломано, вы можете захотеть убить ваше приложение, чтобы клиенты получали 503 при попытке подключения.
Затем, в некоторых сценариях,вы можете захотеть убить ваше приложение, а затем позволить другой службе перезагрузить его, когда база данных снова станет доступной.Это во многом зависит от инфраструктуры, и вы также можете не убивать ваше приложение никогда.
Если у вас нет инфраструктуры, которая контролирует работоспособность и перезагрузку вашего веб-сервиса, вероятно, безопаснее никогдапусть ваше приложение умирает.Сказано, что хорошо по крайней мере использовать такие инструменты, как nodemon или PM2, чтобы гарантировать, что ваше приложение будет перезапущено после выхода из строя.
Бонус: почему вы не должны выбрасывать ошибки в обратных вызовах
Брошенные ошибкираспространяется через callstack.Допустим, у вас есть функция A, которая вызывает B, которая затем вызывает C. Затем C выдает ошибку.Все они имеют только синхронный код.
В этом случае ошибка распространяется на B и, если она не catch
, она распространяется на A и т. Д.
Теперьскажем, что вместо этого C не выдает ошибку самостоятельно, а вызывает fs.readFile(path, callback)
.В функции обратного вызова выдается ошибка.
Здесь, когда вызывается обратный вызов и выдается ошибка, A уже завершена и покинула стек давно, сотни миллисекунд назад, может быть, даже больше.
Это означает, что любой catch
блок в A не поймает ошибку, потому что даже там уже нет :
function bootstrapTimeout() {
try {
setTimeout(() => {
throw new Error('foo');
console.log('paco');
}, 200);
} catch (e) {
console.log('error trapped!');
}
}
function bootstrapInterval() {
setInterval(() => {
console.log('interval')
}, 50);
}
console.log('start');
bootstrapTimeout();
bootstrapInterval();
Если вы запустите этот фрагмент,вы увидите, как ошибка достигает верхнего уровня и завершает процесс, даже если строка throw new Error('foo');
была помещена в блок try / catch.
ошибка, интерфейс результата
Вместо использованияОшибки для обработки исключений в асинхронном коде, у node.js стандартное поведение - выставлять интерфейс (error, result)
для каждого обратного вызова, передаваемого асинхронному методу.Если, например, fs.readFile
происходит неправильно из-за того, что имя файла не существует, он не выдает ошибку, он вызывает обратный вызов с соответствующей ошибкой в качестве параметра error
.
Like:
fs.readFile('notexists.png', (error, callback) => {
if(error){
// foo
}
else {
http.post('http://something.com', result, (error, callback) => {
if(error){
// oops, something went wrong with an http request
} else {
// keep working
// etc.
// maybe more callbacks, always with the dreadful 'if (error)'...
}
})
}
});
Вы всегда управляете ошибками в асинхронных операциях в обратном вызове, вы никогда не должны бросать.
Теперь это боль в заднице.Обещания позволяют намного лучше контролировать ошибки , потому что вы можете контролировать асинхронные ошибки в одном единственном блоке перехвата :
fsReadFilePromise('something.png')
.then(res => someHttpRequestPromise(res))
.then(httpResponse => someOtherAsyncMethod(httpResponse))
.then(_ => maybeSomeLoggingOrWhatever() )
.catch(e => {
// here you can control any error thrown in the previous chain.
});
А также есть асинхронное / ожидание, которое позволяет вам смешивать асинхронный и синхронизирующий код иОтноситесь к отказам от обещаний в блоках улова:
await function main() {
try {
a(); // some sync code
await b(); // some promise
} catch(e) {
console.log(e); // either an error throw in a() or a promise rejection reason in b();
}
}
Однако имейте в виду, что await
не магия, и вам действительно нужно хорошо понимать обещания и асинхронность, чтобы правильно их использовать.
В конце вы всегда получаете один поток управления ошибками для синхронных ошибок через try/catch
, а другой - для асинхронных ошибок, через параметры обратного вызова или отклонения обещания.
Обратные вызовы может использовать try/catch
при использовании синхронных API, но никогда не должен throw
.Любая функция может использовать catch
для обработки синхронных ошибок, но не может полагаться на блоки catch
для обработки асинхронных ошибок.Вроде грязный.