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

'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;
}