You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
192 lines
5.8 KiB
JavaScript
192 lines
5.8 KiB
JavaScript
8 years ago
|
'use strict';
|
||
|
|
||
|
const debug = require("debug")("unhandled-rejection");
|
||
|
const EventEmitter = require("events").EventEmitter;
|
||
|
const rejectionStore = require("./rejection-store");
|
||
|
|
||
|
let unhandledTimeout = 60 * 1000;
|
||
|
let unhandledPromises = [];
|
||
|
|
||
|
module.exports = function(options = {}) {
|
||
|
let emitter = new EventEmitter();
|
||
|
let store = rejectionStore(options.timeout);
|
||
|
|
||
|
function extractPromiseRejectionEvent(event) {
|
||
|
let errorData = {};
|
||
|
|
||
|
if (event.detail != null && event.detail.reason != null) {
|
||
|
errorData.error = event.detail.reason;
|
||
|
} else if (event.reason != null) {
|
||
|
errorData.error = event.reason;
|
||
|
}
|
||
|
|
||
|
if (event.detail != null && event.detail.promise != null) {
|
||
|
errorData.promise = event.detail.promise;
|
||
|
} else if (event.promise != null) {
|
||
|
errorData.promise = event.promise;
|
||
|
}
|
||
|
|
||
|
return errorData;
|
||
|
}
|
||
|
|
||
|
function handleEvent(event, errorData) {
|
||
|
if (event != null && event.preventDefault != null && typeof event.preventDefault === "function") {
|
||
|
event.preventDefault();
|
||
|
}
|
||
|
|
||
|
if (errorData == null) {
|
||
|
if (event != null) {
|
||
|
errorData = extractPromiseRejectionEvent(event);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return errorData;
|
||
|
}
|
||
|
|
||
|
function deduplicateError(errorData) {
|
||
|
/* This is to deal with the case where an unhandled rejection comes in through
|
||
|
* more than one event interface, eg. in browser code. It returns a boolean
|
||
|
* indicating whether to continue the emitting process.
|
||
|
*/
|
||
|
return (!store.exists(errorData));
|
||
|
}
|
||
|
|
||
|
function handleUnhandledRejection(event, errorData) {
|
||
|
debug("Got unhandledRejection");
|
||
|
|
||
|
let normalizedErrorData = handleEvent(event, errorData);
|
||
|
|
||
|
if (deduplicateError(normalizedErrorData)) {
|
||
|
store.register(normalizedErrorData);
|
||
|
|
||
|
debug("Emitting unhandledRejection...");
|
||
|
emitter.emit("unhandledRejection", normalizedErrorData.error, normalizedErrorData.promise);
|
||
|
} else {
|
||
|
debug("Ignoring unhandledRejection as duplicate");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function handleRejectionHandled(event, errorData) {
|
||
|
debug("Got rejectionHandled");
|
||
|
let normalizedErrorData = handleEvent(event, errorData);
|
||
|
store.unregister(normalizedErrorData)
|
||
|
|
||
|
debug("Emitting rejectionHandled...");
|
||
|
emitter.emit("rejectionHandled", normalizedErrorData.error, normalizedErrorData.promise);
|
||
|
}
|
||
|
|
||
|
function onunhandledrejectionHandler(reason, promise) {
|
||
|
if (promise != null && promise.then != null) {
|
||
|
/* First argument is an error. */
|
||
|
handleUnhandledRejection(null, {error: reason, promise: promise});
|
||
|
} else {
|
||
|
/* First argument is an event. */
|
||
|
handleUnhandledRejection(reason);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
function onrejectionhandledHandler(promise) {
|
||
|
if (promise.then != null) {
|
||
|
/* First argument is a Promise. */
|
||
|
let errorData = store.find(promise);
|
||
|
|
||
|
if (errorData != null) {
|
||
|
handleRejectionHandled(null, errorData);
|
||
|
}
|
||
|
} else {
|
||
|
/* First argument is an event. */
|
||
|
handleRejectionHandled(promise);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
function configureContextHandlers(context) {
|
||
|
if (typeof context.onunhandledrejection === "function") {
|
||
|
debug("Wrapping previous handler for <context>.onunhandledrejection");
|
||
|
let _oldHandler = context.onunhandledrejection;
|
||
|
context.onunhandledrejection = function(reason, promise) {
|
||
|
onunhandledrejectionHandler(reason, promise);
|
||
|
_oldHandler(event);
|
||
|
}
|
||
|
} else {
|
||
|
context.onunhandledrejection = onunhandledrejectionHandler;
|
||
|
}
|
||
|
|
||
|
if (typeof context.onrejectionhandled === "function") {
|
||
|
debug("Wrapping previous handler for <context>.onrejectionhandled");
|
||
|
let _oldHandler = context.onrejectionhandled;
|
||
|
context.onrejectionhandled = function(promise) {
|
||
|
onrejectionhandledHandler(promise);
|
||
|
_oldHandler(promise);
|
||
|
}
|
||
|
} else {
|
||
|
context.onrejectionhandled = onrejectionhandledHandler;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Bundlers like Webpack will shim `process`, but set its `.browser` property to `true`. */
|
||
|
let isWebWorker = (typeof WorkerGlobalScope !== "undefined");
|
||
|
let isNode = (typeof process !== "undefined" && process.browser !== true);
|
||
|
let isBrowser = (!isNode && !isWebWorker && typeof document !== "undefined");
|
||
|
|
||
|
if (isNode) {
|
||
|
debug("Detected environment: Node.js");
|
||
|
|
||
|
/* Bluebird, ES6 in Node.js */
|
||
|
process.on("unhandledRejection", (error, promise) => {
|
||
|
let errorData = {
|
||
|
error: error,
|
||
|
promise: promise
|
||
|
};
|
||
|
|
||
|
handleUnhandledRejection(null, errorData);
|
||
|
});
|
||
|
|
||
|
process.on("rejectionHandled", (promise) => {
|
||
|
let errorData = store.find(promise);
|
||
|
|
||
|
if (errorData != null) {
|
||
|
handleRejectionHandled(null, errorData);
|
||
|
}
|
||
|
});
|
||
|
} else if (isWebWorker) {
|
||
|
debug("Detected environment: WebWorker");
|
||
|
|
||
|
/* Yaku, Bluebird, WHATWG Legacy(?)
|
||
|
* The Bluebird documentation says self.addEventListener, but it seems to use on* handlers instead. */
|
||
|
configureContextHandlers(self);
|
||
|
|
||
|
/* WHATWG */
|
||
|
self.addEventListener("unhandledrejection", handleUnhandledRejection);
|
||
|
self.addEventListener("rejectionhandled", handleRejectionHandled);
|
||
|
|
||
|
/* WhenJS (note the capitalization) - currently broken */
|
||
|
self.addEventListener("unhandledRejection", handleUnhandledRejection);
|
||
|
self.addEventListener("rejectionHandled", handleRejectionHandled);
|
||
|
} else if (isBrowser) {
|
||
|
debug("Detected environment: Browser");
|
||
|
|
||
|
if (window.addEventListener != null) {
|
||
|
debug("addEventListener is available, registering events...");
|
||
|
|
||
|
/* Bluebird, WHATWG */
|
||
|
window.addEventListener("unhandledrejection", handleUnhandledRejection);
|
||
|
window.addEventListener("rejectionhandled", handleRejectionHandled);
|
||
|
|
||
|
/* WhenJS (note the capitalization) - currently broken */
|
||
|
window.addEventListener("unhandledRejection", handleUnhandledRejection);
|
||
|
window.addEventListener("rejectionHandled", handleRejectionHandled);
|
||
|
}
|
||
|
|
||
|
/* We will need to attempt to catch unhandled rejections using both the modern and
|
||
|
* legacy APIs, because Yaku only supports the latter, *even* in modern browsers.
|
||
|
*/
|
||
|
debug("Configuring window.on* handlers...");
|
||
|
|
||
|
/* Bluebird (Legacy API), WHATWG (Legacy API), Yaku */
|
||
|
configureContextHandlers(window);
|
||
|
}
|
||
|
|
||
|
return emitter;
|
||
|
}
|