Browse Source

Make unhandled Promise errors throw an exception

A promise will throw an exception unless an error handler is attached in the
same "tick" that the error is emitted. This is to avoid silent promise
failures.
v0.7.4-release
Felix Geisendörfer 15 years ago
committed by Ryan Dahl
parent
commit
bfd3144861
  1. 42
      doc/api.txt
  2. 27
      src/node.js
  3. 23
      test/mjsunit/test-promise.js

42
doc/api.txt

@ -304,7 +304,47 @@ If you created the promise (by doing +new events.Promise()+) then call
the moment due to a bug; use +emitSuccess+ instead.) the moment due to a bug; use +emitSuccess+ instead.)
+promise.emitError(arg1, arg2, ...)+ :: +promise.emitError(arg1, arg2, ...)+ ::
Emits the +"error"+ event. Emits the +"error"+ event. If a no error handler is attached to the promise
between +promise.emitError()+ and +process.nextTick()+, an exception is
thrown.
+
To explain the exception behavior, assume you have a "computeQuestion"
function as follows:
+
----------------------------------------
var events = require('events');
function computeQuestion(answer) {
var promise = new events.Promise();
if (answer !== 42) {
promise.emitError('wrong answer');
return promise;
}
// compute the question for 42
return promise;
}
----------------------------------------
+
You can stop an exception to be thrown here by attaching an errback handler
right away (in the same event loop tick) like this:
+
----------------------------------------
computeQuestion(23).addErrback(function() {
// No exception will be thrown
});
----------------------------------------
+
However, if you try to attach the error handler in a later tick, the promise
will already have thrown an exception:
+
----------------------------------------
var promise = computeQuestion(23);
setTimeout(function() {
promise.addErrback(function() {
// This will never execute, the promise already threw an exception
});
}, 1000);
----------------------------------------
+promise.timeout(timeout = undefined)+ :: +promise.timeout(timeout = undefined)+ ::
If the +timeout+ parameter is provided, the promise will emit an +"error"+ If the +timeout+ parameter is provided, the promise will emit an +"error"+

27
src/node.js

@ -289,24 +289,31 @@ var eventsModule = createInternalModule('events', function (exports) {
this._values = Array.prototype.slice.call(arguments); this._values = Array.prototype.slice.call(arguments);
this.emit.apply(this, ['error'].concat(this._values)); this.emit.apply(this, ['error'].concat(this._values));
if (this.listeners('error').length == 0) {
var self = this;
process.nextTick(function() {
if (self.listeners('error').length == 0) {
throw new Error('Unhandled emitError: '+JSON.stringify(self._values));
}
});
}
}; };
exports.Promise.prototype.addCallback = function (listener) { exports.Promise.prototype.addCallback = function (listener) {
if (!this.hasFired) { if (this.hasFired) {
return this.addListener("success", listener); return listener.apply(this, this._values);
} }
listener.apply(this, this._values); return this.addListener("success", listener);
return this;
}; };
exports.Promise.prototype.addErrback = function (listener) { exports.Promise.prototype.addErrback = function (listener) {
if (!this.hasFired) { if (this.hasFired) {
return this.addListener("error", listener); listener.apply(this, this._values);
} }
listener.apply(this, this._values); return this.addListener("error", listener);
return this;
}; };
/* Poor Man's coroutines */ /* Poor Man's coroutines */
@ -1010,10 +1017,6 @@ process.mainModule = createModule(".");
var loadPromise = new events.Promise(); var loadPromise = new events.Promise();
process.mainModule.load(process.ARGV[1], loadPromise); process.mainModule.load(process.ARGV[1], loadPromise);
loadPromise.addErrback(function(e) {
throw e;
});
// All our arguments are loaded. We've evaluated all of the scripts. We // All our arguments are loaded. We've evaluated all of the scripts. We
// might even have created TCP servers. Now we enter the main eventloop. If // might even have created TCP servers. Now we enter the main eventloop. If
// there are no watchers on the loop (except for the ones that were // there are no watchers on the loop (except for the ones that were

23
test/mjsunit/test-promise.js

@ -9,6 +9,8 @@ var
a2: 1, a2: 1,
b1: 1, b1: 1,
b2: 1, b2: 1,
c1: 1,
d1: 1,
}; };
// Test regular & late callback binding // Test regular & late callback binding
@ -37,6 +39,27 @@ b.addErrback(function(value) {
expectedCallbacks.b2--; expectedCallbacks.b2--;
}); });
// Test late errback binding
var c = new Promise();
c.emitError(TEST_VALUE);
c.addErrback(function(value) {
assert.equal(TEST_VALUE, value);
expectedCallbacks.c1--;
});
// Test errback exceptions
var d = new Promise();
d.emitError(TEST_VALUE);
process.addListener('uncaughtException', function(e) {
if (e.name === "AssertionError") {
throw e;
}
expectedCallbacks.d1--;
assert.ok(e.message.match(/unhandled emitError/i));
});
process.addListener('exit', function() { process.addListener('exit', function() {
for (var name in expectedCallbacks) { for (var name in expectedCallbacks) {
var count = expectedCallbacks[name]; var count = expectedCallbacks[name];

Loading…
Cancel
Save