Skip to content

Commit

Permalink
Test implementation of done() from promises-aplus/unhandled-rejection…
Browse files Browse the repository at this point in the history
  • Loading branch information
lazd committed Jan 14, 2013
1 parent 8ed1037 commit 2be726c
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 13 deletions.
60 changes: 47 additions & 13 deletions Pinky.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@ var Pinky = function() {
this.executeOnRejectedCallback = this.executeOnRejectedCallback.bind(this);
this.fulfill = this.fulfill.bind(this);
this.reject = this.reject.bind(this);
this.then = this.then.bind(this);
this.done = this.done.bind(this);
var then = this.then = this.then.bind(this);

// Create a thenable property that only has this.then
// We'll return this inside of then() in place of the Pinky instance
// This can also be used by implementations that create Pinky instances
this.thenable = this.promise = {
then: this.then
// Create a thenable property, this.promise, that only has then() and done()
// This should be returned by functions that create and use Pinky instances
this.promise = {
then: function(onFulfilled, onRejected) {
// Return the thenable property of the Pinky instance returned by then()
return then(onFulfilled, onRejected).promise;
},
done: this.done
};
};

Expand Down Expand Up @@ -118,13 +122,17 @@ Pinky.prototype.fulfill = function(fulfilledValue) {
// 3.1.2.2: When in fulfilled, a promise must have a value, which must not change.
this.value = fulfilledValue;

// 3.2.2.1 Call each onFulfilled after promise is fulfilled, with promise’s fulfillment value as its first argument.
this.executeCallbacks(true);
// done() support:
// A promise that isDone should _never_ have callbacks to execute
if (!this.isDone) {
// 3.2.2.1 Call each onFulfilled after promise is fulfilled, with promise’s fulfillment value as its first argument.
this.executeCallbacks(true);
}

return this;
};

Pinky.prototype.reject =function(reasonRejected) {
Pinky.prototype.reject = function(reasonRejected) {
// 3.1.1.1: When in pending, a promise may transition to the rejected state.
// 3.1.2.1: When in fulfilled, a promise must not transition to any other state.
if (this.state !== this.states.PENDING) return;
Expand All @@ -134,16 +142,28 @@ Pinky.prototype.reject =function(reasonRejected) {
// 3.1.3.2: When in rejected, a promise must have a reason, which must not change.
this.reason = reasonRejected;

// 3.2.3.1 Call each onRejected after promise is rejected, with promise’s rejection reason as its first argument.
this.executeCallbacks(false);
// done() support:
// A promise that isDone should always throw when rejected and should _never_ have callbacks to execute
if (this.isDone) {
throw this.reason;
}
else {
// 3.2.3.1 Call each onRejected after promise is rejected, with promise’s rejection reason as its first argument.
this.executeCallbacks(false);
}

return this;
};

Pinky.prototype.then = function(onFulfilled, onRejected) {
Pinky.prototype.then = function(onFulfilled, onRejected, isDone) {
// 3.2.6: Create a new promise
var promise2 = new Pinky();

// done() support:
// Set isDone on the created promise
if (isDone)
promise2.isDone = true;

var executeCallback = this.executeCallback;
var value = this.value;
var reason = this.reason;
Expand Down Expand Up @@ -183,7 +203,21 @@ Pinky.prototype.then = function(onFulfilled, onRejected) {
};

// 3.2.6: Return a promise
return promise2.thenable;
// Return an actual Pinky instance.
// Implementors should return the promise property, not the Pinky instance itself
return promise2;
};

// done() support:
// A test implementation of https://github.com/promises-aplus/unhandled-rejections-spec/issues/5
Pinky.prototype.done = function(onFulfilled, onRejected) {
// Done should always create a new promise, do so via then()
// The new promise should be considered done, so pass true as the 3rd argument
// It will be rejected when this promise is rejected
var promise2 = this.then(onFulfilled, onRejected, true);

// done() should return undefined, so no return statement is needed
};


if (typeof module !== 'undefined') module.exports = Pinky;
11 changes: 11 additions & 0 deletions examples/done/done_crash1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const adapter = require('../../test/pinky-adapter');

// Create a rejected promise
var promise = adapter.rejected(new Error('Crash!'));

/*
A crash should occur:
There are no onRejected handlers to handle the rejection.
*/

promise.done();
13 changes: 13 additions & 0 deletions examples/done/done_crash2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const adapter = require('../../test/pinky-adapter');

// Create a pending promise
var promise = adapter.pending();

/*
A crash should occur:
There are no onRejected handlers to handle the rejection.
*/

promise.done();

promise.reject(new Error('Crash!'));
18 changes: 18 additions & 0 deletions examples/done/done_crash3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const adapter = require('../../test/pinky-adapter');

// Create a rejected promise
var promise = adapter.rejected(new Error('Crash!'));

/*
A crash should occur:
There are no onRejected handlers to handle the rejection.
*/

promise
.then(
function(value) {
console.log('Fulfilled 1: '+value);
},
null
)
.done();
21 changes: 21 additions & 0 deletions examples/done/done_crash4.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const adapter = require('../../test/pinky-adapter');

// Create a rejected promise
var promise = adapter.rejected(new Error('Crash!'));

/*
A crash should occur:
Since the first onRejected handler throws, and there are no other onRejected handlers
*/

promise
.then(
function(value) {
console.log('Fulfilled 1: '+value);
},
function(reason) {
console.log('Rejected 1: '+reason);
throw reason; // Throw again to reject the next promise
}
)
.done();
30 changes: 30 additions & 0 deletions examples/done/done_crash5.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const adapter = require('../../test/pinky-adapter');

// Create a rejected promise
var promise = adapter.rejected(new Error('Crash!'));

/*
A crash should occur:
Since the first onRejected handler throws, the next onRejected handler throws, and there are no other onRejected handlers to handle the rejection
*/

promise
.then(
function(value) {
console.log('Fulfilled 1: '+value);
},
function(reason) {
console.log('Rejected 1: '+reason);
throw reason; // Throw again to reject the next promise
}
)
.then(
function(value) {
console.log('Fulfilled 2: '+value);
},
function(reason) {
console.log('Rejected 2: '+reason);
throw reason; // Causes a crash; no more onRejection handlers
}
)
.done();
19 changes: 19 additions & 0 deletions examples/done/done_crash6.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const adapter = require('../../test/pinky-adapter');

// Create a fulfilled promise
var promise = adapter.fulfilled('A value');

/*
A crash should occur:
There are no onRejected handlers to handle the rejection caused by throwing within the onFulfilled handler
*/

promise
.then(
function(value) {
console.log('Fulfilled with '+value);
throw new Error('Crash!');
},
null
)
.done();
10 changes: 10 additions & 0 deletions test/pinky-adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,15 @@ const Pinky = require('../Pinky');
module.exports = {
pending: function() {
return new Pinky();
},
fulfilled: function(value) {
var pinky = new Pinky();
pinky.fulfill(value);
return pinky.promise;
},
rejected: function(reason) {
var pinky = new Pinky();
pinky.reject(reason);
return pinky.promise;
}
};

0 comments on commit 2be726c

Please sign in to comment.