Skip to content

Commit e2030c7

Browse files
authored
fix: Improve checks for Error in onerror handlers (#1468)
Fixes #1466
1 parent 5208c5e commit e2030c7

File tree

4 files changed

+61
-4
lines changed

4 files changed

+61
-4
lines changed

lib/application.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,13 @@ module.exports = class Application extends Emitter {
195195
*/
196196

197197
onerror(err) {
198-
if (!(err instanceof Error)) throw new TypeError(util.format('non-error thrown: %j', err));
198+
// When dealing with cross-globals a normal `instanceof` check doesn't work properly.
199+
// See https://github.com/koajs/koa/issues/1466
200+
// We can probably remove it once jest fixes https://github.com/facebook/jest/issues/2549.
201+
const isNativeError =
202+
Object.prototype.toString.call(err) === '[object Error]' ||
203+
err instanceof Error;
204+
if (!isNativeError) throw new TypeError(util.format('non-error thrown: %j', err));
199205

200206
if (404 === err.status || err.expose) return;
201207
if (this.silent) return;

lib/context.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,13 @@ const proto = module.exports = {
110110
// to node-style callbacks.
111111
if (null == err) return;
112112

113-
if (!(err instanceof Error)) err = new Error(util.format('non-error thrown: %j', err));
113+
// When dealing with cross-globals a normal `instanceof` check doesn't work properly.
114+
// See https://github.com/koajs/koa/issues/1466
115+
// We can probably remove it once jest fixes https://github.com/facebook/jest/issues/2549.
116+
const isNativeError =
117+
Object.prototype.toString.call(err) === '[object Error]' ||
118+
err instanceof Error;
119+
if (!isNativeError) err = new Error(util.format('non-error thrown: %j', err));
114120

115121
let headerSent = false;
116122
if (this.headerSent || !this.writable) {

test/application/onerror.js

+12
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ describe('app.onerror(err)', () => {
1616
}, TypeError, 'non-error thrown: foo');
1717
});
1818

19+
it('should accept errors coming from other scopes', () => {
20+
const ExternError = require('vm').runInNewContext('Error');
21+
22+
const app = new Koa();
23+
const error = Object.assign(new ExternError('boom'), {
24+
status: 418,
25+
expose: true
26+
});
27+
28+
assert.doesNotThrow(() => app.onerror(error));
29+
});
30+
1931
it('should do nothing if status is 404', () => {
2032
const app = new Koa();
2133
const err = new Error();

test/context/onerror.js

+35-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
'use strict';
32

43
const assert = require('assert');
@@ -205,6 +204,40 @@ describe('ctx.onerror(err)', () => {
205204
});
206205
});
207206

207+
describe('when error from another scope thrown', () => {
208+
it('should handle it like a normal error', async() => {
209+
const ExternError = require('vm').runInNewContext('Error');
210+
211+
const app = new Koa();
212+
const error = Object.assign(new ExternError('boom'), {
213+
status: 418,
214+
expose: true
215+
});
216+
app.use((ctx, next) => {
217+
throw error;
218+
});
219+
220+
const server = app.listen();
221+
222+
const gotRightErrorPromise = new Promise((resolve, reject) => {
223+
app.on('error', receivedError => {
224+
try {
225+
assert.strictEqual(receivedError, error);
226+
resolve();
227+
} catch (e) {
228+
reject(e);
229+
}
230+
});
231+
});
232+
233+
await request(server)
234+
.get('/')
235+
.expect(418);
236+
237+
await gotRightErrorPromise;
238+
});
239+
});
240+
208241
describe('when non-error thrown', () => {
209242
it('should response non-error thrown message', () => {
210243
const app = new Koa();
@@ -248,7 +281,7 @@ describe('ctx.onerror(err)', () => {
248281
});
249282

250283
app.use(async ctx => {
251-
throw {key: 'value'}; // eslint-disable-line no-throw-literal
284+
throw { key: 'value' }; // eslint-disable-line no-throw-literal
252285
});
253286

254287
request(app.callback())

0 commit comments

Comments
 (0)