Skip to content

Commit

Permalink
Add nice unhandled-rejection tracking API as per kriskowal#265.
Browse files Browse the repository at this point in the history
- Closes kriskowal#244 and closes kriskowal#245 since `Q.stopUnhandledRejectionTracking()` removes the `process.on("exit", ...)` handler.
- Closes kriskowal#255 since `Q.stopUnhandledRejectionTracking()` prevents any storage or tracking of unhandled rejections or unhandled rejection reasons.
- Closes kriskowal#265 via the new `Q.getUnhandledReasons()` and `Q.resetUnhandledRejections()` APIs, which are useful mainly for testing and diagnostics.
- Closes kriskowal#278 by slightly reordering the code inside `trackRejection`.
  • Loading branch information
domenic committed May 18, 2013
1 parent 686637c commit 9dcf462
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 26 deletions.
92 changes: 68 additions & 24 deletions q.js
Original file line number Diff line number Diff line change
Expand Up @@ -729,13 +729,16 @@ function isRejected(object) {
return isPromise(object) && "exception" in object;
}

//// BEGIN UNHANDLED REJECTION TRACKING

// This promise library consumes exceptions thrown in handlers so they can be
// handled by a subsequent promise. The exceptions get added to this array when
// they are created, and removed when they are handled. Note that in ES6 or
// shimmed environments, this would naturally be a `Set`.
var unhandledReasons = Q.unhandledReasons = [];
var unhandledReasons = [];
var unhandledRejections = [];
var unhandledReasonsDisplayed = false;
var trackUnhandledRejections = true;
function displayUnhandledReasons() {
if (
!unhandledReasonsDisplayed &&
Expand All @@ -750,28 +753,75 @@ function displayUnhandledReasons() {
unhandledReasonsDisplayed = true;
}

Q.resetUnhandledRejections = function () {
function logUnhandledReasons() {
for (var i = 0; i < unhandledReasons.length; i++) {
var reason = unhandledReasons[i];
if (reason && typeof reason.stack !== "undefined") {
console.warn("Unhandled rejection reason:", reason.stack);
} else {
console.warn("Unhandled rejection reason (no stack):", reason);
}
}
}

function resetUnhandledRejections() {
unhandledReasons.length = 0;
unhandledRejections.length = 0;
unhandledReasonsDisplayed = false;
};

// Show unhandled rejection reasons if Node exits without handling an
// outstanding rejection. (Note that Browserify presently produces a process
// global without the `EventEmitter` `on` method.)
if (typeof process !== "undefined" && process.on) {
process.on("exit", function () {
for (var i = 0; i < unhandledReasons.length; i++) {
var reason = unhandledReasons[i];
if (reason && typeof reason.stack !== "undefined") {
console.warn("Unhandled rejection reason:", reason.stack);
} else {
console.warn("Unhandled rejection reason (no stack):", reason);
}
if (!trackUnhandledRejections) {
trackUnhandledRejections = true;

// Show unhandled rejection reasons if Node exits without handling an
// outstanding rejection. (Note that Browserify presently produces a
// `process` global without the `EventEmitter` `on` method.)
if (typeof process !== "undefined" && process.on) {
process.on("exit", logUnhandledReasons);
}
});
}
}

function trackRejection(promise, reason) {
if (!trackUnhandledRejections) {
return;
}

unhandledRejections.push(promise);
unhandledReasons.push(reason);
displayUnhandledReasons();
}

function untrackRejection(promise, reason) {
if (!trackUnhandledRejections) {
return;
}

var at = array_indexOf(unhandledRejections, promise);
if (at !== -1) {
unhandledRejections.splice(at, 1);
unhandledReasons.splice(at, 1);
}
}

Q.resetUnhandledRejections = resetUnhandledRejections;

Q.getUnhandledReasons = function () {
// Make a copy so that consumers can't interfere with our internal state.
return unhandledReasons.slice();
};

Q.stopUnhandledRejectionTracking = function () {
resetUnhandledRejections();
if (typeof process !== "undefined" && process.on) {
process.removeListener("exit", logUnhandledReasons);
}
trackUnhandledRejections = false;
};

resetUnhandledRejections();

//// END UNHANDLED REJECTION TRACKING

/**
* Constructs a rejected promise.
* @param reason value describing the failure
Expand All @@ -782,11 +832,7 @@ function reject(reason) {
"when": function (rejected) {
// note that the error has been handled
if (rejected) {
var at = array_indexOf(unhandledRejections, this);
if (at !== -1) {
unhandledRejections.splice(at, 1);
unhandledReasons.splice(at, 1);
}
untrackRejection(this);
}
return rejected ? rejected(reason) : this;
}
Expand All @@ -797,9 +843,7 @@ function reject(reason) {
}, reason, true);

// Note that the reason has not been handled.
displayUnhandledReasons();
unhandledRejections.push(rejection);
unhandledReasons.push(reason);
trackRejection(rejection, reason);

return rejection;
}
Expand Down
34 changes: 32 additions & 2 deletions spec/q-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2415,14 +2415,44 @@ describe("unhandled rejection reporting", function () {
deferred.resolve();
deferred.reject();

expect(Q.unhandledReasons.length).toEqual(0);
expect(Q.getUnhandledReasons().length).toEqual(0);
});

it("doesn't report when you chain off a rejection", function () {
return Q.reject("this will be handled").get("property").fail(function () {
// now it should be handled.
}).fin(function() {
expect(Q.unhandledReasons.length).toEqual(0);
expect(Q.getUnhandledReasons().length).toEqual(0);
});
});

it("reports the most basic case", function () {
Q.reject("a reason");

expect(Q.getUnhandledReasons()).toEqual(["a reason"]);
});

it("doesn't let you mutate the internal array", function () {
Q.reject("a reason");

Q.getUnhandledReasons().length = 0;
expect(Q.getUnhandledReasons()).toEqual(["a reason"]);
});

it("resets after calling `Q.resetUnhandledRejections`", function () {
Q.reject("a reason");

Q.resetUnhandledRejections();
expect(Q.getUnhandledReasons()).toEqual([]);
});

it("stops tracking after calling `Q.stopUnhandledRejectionTracking`", function () {
Q.reject("a reason");

Q.stopUnhandledRejectionTracking();

Q.reject("another reason");

expect(Q.getUnhandledReasons()).toEqual([]);
});
});

0 comments on commit 9dcf462

Please sign in to comment.