Skip to content

Commit

Permalink
feat: respond to unhandled promise rejection events
Browse files Browse the repository at this point in the history
  • Loading branch information
teclone committed Nov 12, 2018
1 parent 5e8eff2 commit 4cf5c59
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 33 deletions.
33 changes: 11 additions & 22 deletions src/modules/Engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,13 @@ export default class Engine {
*@param {http.IncomingMessage} request - the request instance
*@param {Response} response - the response instance
*@param {Array} [middlewares] - Array of middlewares
*@param {Logger} logger - the logger instance
*/
constructor(url, method, request, response, middlewares, logger) {
constructor(url, method, request, response, middlewares) {

this.resolved = false;
this.request = request;
this.response = response;
this.middlewares = Util.isArray(middlewares)? middlewares : [];
this.logger = logger;

this.url = url.toLowerCase().replace(/[#?].*$/, '').replace(/^\/+/, '').replace(/\/+$/, '');
this.method = method.toUpperCase();
Expand Down Expand Up @@ -67,29 +65,20 @@ export default class Engine {

for (const middleware of this.middlewares) {
cont = false;
try {
await middleware(this.request, this.response, next, ...params);
//if middleware says continue and response.end is not called, then continue
if (cont && !this.response.finished)
continue;

//else if the response is not ended, end it
if (!this.response.finished)
this.response.end();
await middleware(this.request, this.response, next, ...params);
//if middleware says continue and response.end is not called, then continue
if (cont && !this.response.finished)
continue;

return;
}
catch(ex) {
this.logger.fatal(ex, this.response);
}
}
//else if the response is not ended, end it
if (!this.response.finished)
this.response.end();

try {
await callback(this.request, this.response, ...params);
}
catch(ex) {
this.logger.fatal(ex, this.response);
return;
}

await callback(this.request, this.response, ...params);
}

/**
Expand Down
35 changes: 25 additions & 10 deletions src/modules/Server.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,13 @@ export default class {
this.logger.fatal(err);
}

/**
* responds to un handled promise rejection
*/
unhandledPRHandler(reason, p, response) {
this.logger.fatal(reason, response);
}

/**
* handle request data event
*@param {http.IncomingMessage} request - the request object
Expand All @@ -235,7 +242,7 @@ export default class {
*@param {number} bufferDetails.size - the buffer size
*@param {Array} bufferDetails.buffers - array containing chunks of buffer data
*/
onRequestEnd(request, response, bufferDetails) {
async onRequestEnd(request, response, bufferDetails) {

//profile the response time
response.startTime = new Date();
Expand All @@ -257,16 +264,24 @@ export default class {

this.parseRequestData(request, url, bufferDetails.buffer);

this.cordinateRoutes(url, method, request, response).then(status => {
if (status)
return;
const promiseRejectionHandler = Util.generateCallback(
this.unhandledPRHandler,
this,
response
);
process.on('unhandledRejection', promiseRejectionHandler);

//send 404 response if router did not resolved
let httpErrors = this.config.httpErrors;
this.fileServer.serveHttpErrorFile(
response, 404, httpErrors.baseDir, httpErrors['404']
);
});
const status = await this.cordinateRoutes(url, method, request, response);

process.off('unhandledRejection', promiseRejectionHandler);
if (status)
return;

//send 404 response if router did not resolved
let httpErrors = this.config.httpErrors;
this.fileServer.serveHttpErrorFile(
response, 404, httpErrors.baseDir, httpErrors['404']
);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion test/modules/App.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ describe('App', function() {
});

describe('#listen(port?, callback?)', function() {
it(`should start the server at the given port, calling the callback method once the
it(`should start the server at the given port, calling the callback method once the
server gets started`, function(done) {
app.listen(null, function() {
app.close(function() {
Expand Down
25 changes: 25 additions & 0 deletions test/modules/Server.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -385,5 +385,30 @@ describe('Server', function() {
});
});
});

it (`should handle all forms of errors, unhandled promises that arises from any of
the middleware, or route callbacks`, function(done) {

server.router.all('/', () => {
return new Promise((resolve) => {
resolve(Math.abs(-10));
}).then( () => {
throw new Error('chai');
});
});

server.listen(null, () => {
const spy = sinon.spy(server, 'unhandledPRHandler');
request.get('http://localhost:4000/', () => {

expect(spy.called).to.be.true;
spy.restore();

server.close(() => {
done();
});
});
});
});
});
});

0 comments on commit 4cf5c59

Please sign in to comment.