Estoy tratando de hacer que el registro de errores de producción sea el predeterminado y mostrarle al usuario las cosas adicionales solo si la environment variable es development . Estoy tratando de hacer esto de la siguiente manera a continuación, pero recibo un mensaje que dice Cannot set headers after they are sent to the client .
const ErrorClass = require('../routes/utils/ErrorClass'); const prodDBCastError = err => { const message = `Invalid ${err.path}: ${err.value}`; return new ErrorClass(message, 400); }; const prodDBDuplicateFieldsError = err => { const value = err.errmsg.match(/(["'])(\\?.)*?\1/)[0]; const message = `Duplicate field value: ${value}.`; return new ErrorClass(message, 400); }; const prodDBDValidationError = err => { const errors = Object.values(err.errors).map(el => el.message); const message = `Invalid input data. ${errors.join('. ')}`; return new ErrorClass(message, 400); }; const handleBadRequestDB = err => { const errors = err.message; const message = `Fixes: ${errors}`; return new ErrorClass(message, 400); }; const sendProdError = (err, res) => { if (err.isOperationalError){ res.status(err.status).json({ status: err.status, message: err.message, }); } else { res.status(500).json({ status: 'error', message: 'Server Issue.', }); } }; const sendVerboseDevError = (err, res) => { logger.error(err); err.status = err.status || 500; res.status(err.status).json({ status: err.status, name: err.name, path: err.path, errors: err.errors, message: err.message, stack: err.stack, }); }; module.exports = (err, req, res, next) => { if (process.env.APP_ENV === 'development'){ sendVerboseDevError(err, res); } if (err.name === 'CastError') {err = prodDBCastError(err);} if (err.name === 'MongoError') {err = prodDBDuplicateFieldsError(err);} if (err.name === 'ValidationError') {err = prodDBDValidationError(err);} if (err.name === 'Bad Request') {err = handleBadRequestDB(err);} sendProdError(err, res); };Así es como se ve mi ErrorClass:
class ErrorClass extends Error { constructor(message, status) { super(message); this.status = status; this.isOperationalError = true; Error.captureStackTrace(this, this.constructor); } } module.exports = ErrorClass;Te recomendaría cambiar tu código así.
Primero, evita llamar a res.json varias veces y también solo verifica si la aplicación se ejecuta en modo de desarrollo una vez. No hay necesidad de verificarlo con cada solicitud.
var devHandler = (err, req, res, next) => { logger.error(err); err.status = err.status || 500; res.status(err.status).json({ status: err.status, name: err.name, path: err.path, errors: err.errors, message: err.message, stack: err.stack, }); }; var prodHandler = (err, req, res, next) => { if (err.name === 'CastError') {err = prodDBCastError(err);} if (err.name === 'MongoError') {err = prodDBDuplicateFieldsError(err);} if (err.name === 'ValidationError') {err = prodDBDValidationError(err);} if (err.name === 'Bad Request') {err = handleBadRequestDB(err);} if (err.isOperationalError){ res.status(err.status).json({ status: err.status, message: err.message, }); } else { res.status(500).json({ status: 'error', message: 'Server Issue.', }); } }; module.exports = process.env.APP_ENV === 'development' ? devHandler : prodHandler;Esto se debe a que está intentando enviar datos desde sendProdError después de enviar datos desde sendVerboseDevError . res.json está enviando el json al cliente.
La razón detrás de esto se explica aquí https://stackoverflow.com/a/7086621/2232902
El objeto res en Express es una subclase de http.ServerResponse de Node.js (lea la fuente de http.js). Puede llamar a res.setHeader(nombre, valor) con la frecuencia que desee hasta que llame a res.writeHead(statusCode). Después de writeHead, los encabezados se integran y solo puede llamar a res.write(data) y finalmente a res.end(data)
Le recomendaría modificar sendProdError y sendVerboseDevError en constructProdError y constructVerboseDevError y luego enviar desde el mismo punto en el código.
referencia: https://stackoverflow.com/a/733858/2232902
Creo que el problema aquí es con las condiciones múltiples. Si la primera condición se cumple, se ejecutará la función "sendVerboseDevError". Que tiene el siguiente código
res.status(err.status).json({ status: err.status, name: err.name, path: err.path, errors: err.errors, message: err.message, stack: err.stack, });después de esta función, se establecerá el encabezado de respuesta. Luego, el flujo si va al resto de las condiciones si las condiciones son verdaderas y luego nuevamente se llama a otra función que está tratando de establecer la respuesta nuevamente. Es por eso que recibe el error "No se pueden configurar los encabezados después de enviarlos al cliente". Debe llamar al método "siguiente ()" después de configurar la respuesta. Por lo tanto, debe agregar el método "siguiente ()" en la parte inferior de cada condición.
algo como
module.exports = (err, req, res, next) => { if (process.env.APP_ENV === 'development'){ sendVerboseDevError(err, res); next(); } if (err.name === 'CastError') { err = prodDBCastError(err); next(); } if (err.name === 'MongoError') { err = prodDBDuplicateFieldsError(err); next(); } if (err.name === 'ValidationError') { err = prodDBDValidationError(err); next(); } if (err.name === 'Bad Request') { err = handleBadRequestDB(err); next(); } sendProdError(err, res); next(); };la siguiente función terminará la llamada API y devolverá la respuesta y evitará que establezca la respuesta nuevamente después de que cualquier función ejecutada antes de la actual haya establecido la respuesta.
PD: También puede llamar a la función "res.end()" en lugar de "next()".