-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Provide cover for unhandled rejections #3486
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -169,6 +169,107 @@ describe('handler', () => { | |
done(); | ||
}); | ||
}); | ||
|
||
it('catches asynchronous exceptions in promise constructors via the standard \'protect\' mechanism', (done) => { | ||
|
||
const server = new Hapi.Server({ debug: false }); | ||
server.connection(); | ||
|
||
const asyncOperation = function () { | ||
|
||
return new Promise((resolve, reject) => { | ||
|
||
setTimeout(() => { | ||
|
||
throw new Error('This should be caught...'); | ||
}, 100); | ||
}); | ||
}; | ||
|
||
const handler = function (request, reply) { | ||
|
||
return asyncOperation() | ||
.then(reply); | ||
}; | ||
|
||
server.route({ method: 'GET', path: '/', handler }); | ||
|
||
server.inject('/', (res) => { | ||
|
||
expect(res.statusCode).to.equal(500); | ||
expect(res.result.error).to.equal('Internal Server Error'); | ||
done(); | ||
}); | ||
}); | ||
|
||
it('catches synchronous exceptions which lead to unhandled promise rejections when then promise is returned', (done) => { | ||
|
||
const server = new Hapi.Server(); | ||
server.connection(); | ||
|
||
const asyncOperation = function () { | ||
|
||
return new Promise((resolve, reject) => { | ||
|
||
throw new Error('This should be rejected...'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd add a test for your There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call! Adding now There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added the test, it's there now :) |
||
}); | ||
}; | ||
|
||
const handler = function (request, reply) { | ||
|
||
return asyncOperation() | ||
.then(reply); | ||
}; | ||
|
||
server.route({ method: 'GET', path: '/', handler }); | ||
|
||
server.inject('/', (res) => { | ||
|
||
expect(res.statusCode).to.equal(500); | ||
expect(res.result.error).to.equal('Internal Server Error'); | ||
done(); | ||
}); | ||
}); | ||
|
||
it('catches unhandled promise rejections when a promise is returned', (done) => { | ||
|
||
const server = new Hapi.Server(); | ||
server.connection(); | ||
|
||
const handler = function (request, reply) { | ||
|
||
return Promise.reject(new Error('This should be caught.')); | ||
}; | ||
|
||
server.route({ method: 'GET', path: '/', handler }); | ||
|
||
server.inject('/', (res) => { | ||
|
||
expect(res.statusCode).to.equal(500); | ||
expect(res.result.error).to.equal('Internal Server Error'); | ||
done(); | ||
}); | ||
}); | ||
|
||
it('wraps unhandled promise rejections in an error if needed', (done) => { | ||
|
||
const server = new Hapi.Server(); | ||
server.connection(); | ||
|
||
const handler = function (request, reply) { | ||
|
||
return Promise.reject('this should be wrapped in an error'); | ||
}; | ||
|
||
server.route({ method: 'GET', path: '/', handler }); | ||
|
||
server.inject('/', (res) => { | ||
|
||
expect(res.statusCode).to.equal(500); | ||
expect(res.result.error).to.equal('Internal Server Error'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('register()', () => { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
'use strict'; | ||
|
||
// Load modules | ||
|
||
const Code = require('code'); | ||
const Lab = require('lab'); | ||
const Promises = require('../lib/promises'); | ||
|
||
// Declare internals | ||
|
||
const internals = {}; | ||
|
||
|
||
// Test shortcuts | ||
|
||
const lab = exports.lab = Lab.script(); | ||
const describe = lab.describe; | ||
const it = lab.it; | ||
const expect = Code.expect; | ||
|
||
|
||
describe('promise', () => { | ||
|
||
it('recognises a promise as thennable', (done) => { | ||
|
||
const promise = new Promise(() => {}); | ||
expect(Promises.isThennable(promise)).to.equal(true); | ||
|
||
done(); | ||
|
||
}); | ||
|
||
it('recognises invalid input as not thennable', (done) => { | ||
|
||
expect(Promises.isThennable(undefined)).to.equal(false); | ||
expect(Promises.isThennable(null)).to.equal(false); | ||
expect(Promises.isThennable({ })).to.equal(false); | ||
expect(Promises.isThennable({ then: true })).to.equal(false); | ||
|
||
done(); | ||
|
||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it common practice to reject a promise with a non-Error object?!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i sure hope not..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You certainly aren't supposed to, but out in the wild it happens. As to whether hapi should deal with the rejection of a non-error, I can't say. I would probably treat it the same way hapi deals with a non-error thrown in a handler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, can happen. Have seen strings thrown in the past.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hueniverse it is definitely non-conventional to
reject
a non-error object. If done deliberately it would definitely be an anti-pattern. However, there are cases where it will slip in by mistake, here's the most common scenario:In this case, if the library you are using does something goofy like callback with a non-error object, the promise will reject with the non-error object.
I think that with the handling of thrown exceptions in domains we have the same behaviour (because
Boom.wrap
covers whatever is thrown, ensuring that even if what is thrown is not an error, it gets wrapped in one).