Commit 6876cc30 authored by Stefan Penner's avatar Stefan Penner

Merge pull request #63 from stefanpenner/wrapit

WIP RSVP#wrap -> RSVP#resolve (A+ compliant)
parents 8a24804f 60f83a3b
define( define(
["rsvp/events","rsvp/promise","rsvp/node","rsvp/all","rsvp/hash","rsvp/defer","rsvp/config","exports"], ["rsvp/events","rsvp/promise","rsvp/node","rsvp/all","rsvp/hash","rsvp/defer","rsvp/config","rsvp/resolve","exports"],
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) { function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) {
"use strict"; "use strict";
var EventTarget = __dependency1__.EventTarget; var EventTarget = __dependency1__.EventTarget;
var Promise = __dependency2__.Promise; var Promise = __dependency2__.Promise;
...@@ -9,11 +9,13 @@ define( ...@@ -9,11 +9,13 @@ define(
var hash = __dependency5__.hash; var hash = __dependency5__.hash;
var defer = __dependency6__.defer; var defer = __dependency6__.defer;
var config = __dependency7__.config; var config = __dependency7__.config;
var resolve = __dependency8__.resolve;
function configure(name, value) { function configure(name, value) {
config[name] = value; config[name] = value;
} }
__exports__.Promise = Promise; __exports__.Promise = Promise;
__exports__.EventTarget = EventTarget; __exports__.EventTarget = EventTarget;
__exports__.all = all; __exports__.all = all;
...@@ -21,4 +23,5 @@ define( ...@@ -21,4 +23,5 @@ define(
__exports__.defer = defer; __exports__.defer = defer;
__exports__.denodeify = denodeify; __exports__.denodeify = denodeify;
__exports__.configure = configure; __exports__.configure = configure;
__exports__.resolve = resolve;
}); });
...@@ -372,7 +372,8 @@ define("rsvp/promise", ...@@ -372,7 +372,8 @@ define("rsvp/promise",
var noop = function() {}; var noop = function() {};
var Promise = function(resolver) { var Promise = function(resolver) {
var promise = this; var promise = this,
resolved = false;
if (typeof resolver !== 'function') { if (typeof resolver !== 'function') {
throw new TypeError('You must pass a resolver function as the sole argument to the promise constructor'); throw new TypeError('You must pass a resolver function as the sole argument to the promise constructor');
...@@ -383,15 +384,15 @@ define("rsvp/promise", ...@@ -383,15 +384,15 @@ define("rsvp/promise",
} }
var resolvePromise = function(value) { var resolvePromise = function(value) {
if (resolved) { return; }
resolved = true;
resolve(promise, value); resolve(promise, value);
resolvePromise = noop;
rejectPromise = noop;
}; };
var rejectPromise = function(value) { var rejectPromise = function(value) {
if (resolved) { return; }
resolved = true;
reject(promise, value); reject(promise, value);
resolvePromise = noop;
rejectPromise = noop;
}; };
this.on('promise:resolved', function(event) { this.on('promise:resolved', function(event) {
...@@ -499,12 +500,54 @@ define("rsvp/promise", ...@@ -499,12 +500,54 @@ define("rsvp/promise",
}); });
} }
__exports__.Promise = Promise; __exports__.Promise = Promise;
}); });
define("rsvp/resolve",
["rsvp/promise","exports"],
function(__dependency1__, __exports__) {
"use strict";
var Promise = __dependency1__.Promise;
function objectOrFunction(x) {
return typeof x === "function" || (typeof x === "object" && x !== null);
}
function resolve(thenable){
var promise = new Promise(function(resolve, reject){
var then;
try {
if ( objectOrFunction(thenable) ) {
then = thenable.then;
if (typeof then === "function") {
then.call(thenable, resolve, reject);
} else {
resolve(thenable);
}
} else {
resolve(thenable);
}
} catch(error) {
reject(error);
}
});
return promise;
}
__exports__.resolve = resolve;
});
define("rsvp", define("rsvp",
["rsvp/events","rsvp/promise","rsvp/node","rsvp/all","rsvp/hash","rsvp/defer","rsvp/config","exports"], ["rsvp/events","rsvp/promise","rsvp/node","rsvp/all","rsvp/hash","rsvp/defer","rsvp/config","rsvp/resolve","exports"],
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) { function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) {
"use strict"; "use strict";
var EventTarget = __dependency1__.EventTarget; var EventTarget = __dependency1__.EventTarget;
var Promise = __dependency2__.Promise; var Promise = __dependency2__.Promise;
...@@ -513,11 +556,13 @@ define("rsvp", ...@@ -513,11 +556,13 @@ define("rsvp",
var hash = __dependency5__.hash; var hash = __dependency5__.hash;
var defer = __dependency6__.defer; var defer = __dependency6__.defer;
var config = __dependency7__.config; var config = __dependency7__.config;
var resolve = __dependency8__.resolve;
function configure(name, value) { function configure(name, value) {
config[name] = value; config[name] = value;
} }
__exports__.Promise = Promise; __exports__.Promise = Promise;
__exports__.EventTarget = EventTarget; __exports__.EventTarget = EventTarget;
__exports__.all = all; __exports__.all = all;
...@@ -525,6 +570,7 @@ define("rsvp", ...@@ -525,6 +570,7 @@ define("rsvp",
__exports__.defer = defer; __exports__.defer = defer;
__exports__.denodeify = denodeify; __exports__.denodeify = denodeify;
__exports__.configure = configure; __exports__.configure = configure;
__exports__.resolve = resolve;
}); });
window.RSVP = requireModule('rsvp'); window.RSVP = requireModule('rsvp');
......
This diff is collapsed.
...@@ -8,7 +8,8 @@ define( ...@@ -8,7 +8,8 @@ define(
var noop = function() {}; var noop = function() {};
var Promise = function(resolver) { var Promise = function(resolver) {
var promise = this; var promise = this,
resolved = false;
if (typeof resolver !== 'function') { if (typeof resolver !== 'function') {
throw new TypeError('You must pass a resolver function as the sole argument to the promise constructor'); throw new TypeError('You must pass a resolver function as the sole argument to the promise constructor');
...@@ -19,15 +20,15 @@ define( ...@@ -19,15 +20,15 @@ define(
} }
var resolvePromise = function(value) { var resolvePromise = function(value) {
if (resolved) { return; }
resolved = true;
resolve(promise, value); resolve(promise, value);
resolvePromise = noop;
rejectPromise = noop;
}; };
var rejectPromise = function(value) { var rejectPromise = function(value) {
if (resolved) { return; }
resolved = true;
reject(promise, value); reject(promise, value);
resolvePromise = noop;
rejectPromise = noop;
}; };
this.on('promise:resolved', function(event) { this.on('promise:resolved', function(event) {
...@@ -135,5 +136,6 @@ define( ...@@ -135,5 +136,6 @@ define(
}); });
} }
__exports__.Promise = Promise; __exports__.Promise = Promise;
}); });
define(
["rsvp/promise","exports"],
function(__dependency1__, __exports__) {
"use strict";
var Promise = __dependency1__.Promise;
function objectOrFunction(x) {
return typeof x === "function" || (typeof x === "object" && x !== null);
}
function resolve(thenable){
var promise = new Promise(function(resolve, reject){
var then;
try {
if ( objectOrFunction(thenable) ) {
then = thenable.then;
if (typeof then === "function") {
then.call(thenable, resolve, reject);
} else {
resolve(thenable);
}
} else {
resolve(thenable);
}
} catch(error) {
reject(error);
}
});
return promise;
}
__exports__.resolve = resolve;
});
...@@ -5,9 +5,10 @@ import { all } from "rsvp/all"; ...@@ -5,9 +5,10 @@ import { all } from "rsvp/all";
import { hash } from "rsvp/hash"; import { hash } from "rsvp/hash";
import { defer } from "rsvp/defer"; import { defer } from "rsvp/defer";
import { config } from "rsvp/config"; import { config } from "rsvp/config";
import { resolve } from "rsvp/resolve";
function configure(name, value) { function configure(name, value) {
config[name] = value; config[name] = value;
} }
export { Promise, EventTarget, all, hash, defer, denodeify, configure }; export { Promise, EventTarget, all, hash, defer, denodeify, configure, resolve };
\ No newline at end of file
import { config } from "rsvp/config"; import { config } from "rsvp/config";
import { EventTarget } from "rsvp/events"; import { EventTarget } from "rsvp/events";
var noop = function() {};
var Promise = function(resolver) { var Promise = function(resolver) {
var promise = this; var promise = this,
resolved = false;
if (typeof resolver !== 'function') { if (typeof resolver !== 'function') {
throw new TypeError('You must pass a resolver function as the sole argument to the promise constructor'); throw new TypeError('You must pass a resolver function as the sole argument to the promise constructor');
...@@ -15,15 +14,15 @@ var Promise = function(resolver) { ...@@ -15,15 +14,15 @@ var Promise = function(resolver) {
} }
var resolvePromise = function(value) { var resolvePromise = function(value) {
if (resolved) { return; }
resolved = true;
resolve(promise, value); resolve(promise, value);
resolvePromise = noop;
rejectPromise = noop;
}; };
var rejectPromise = function(value) { var rejectPromise = function(value) {
if (resolved) { return; }
resolved = true;
reject(promise, value); reject(promise, value);
resolvePromise = noop;
rejectPromise = noop;
}; };
this.on('promise:resolved', function(event) { this.on('promise:resolved', function(event) {
...@@ -131,4 +130,4 @@ function reject(promise, value) { ...@@ -131,4 +130,4 @@ function reject(promise, value) {
}); });
} }
export { Promise }; export { Promise };
\ No newline at end of file
import { Promise } from "rsvp/promise";
function objectOrFunction(x) {
return typeof x === "function" || (typeof x === "object" && x !== null);
}
function resolve(thenable){
var promise = new Promise(function(resolve, reject){
var then;
try {
if ( objectOrFunction(thenable) ) {
then = thenable.then;
if (typeof then === "function") {
then.call(thenable, resolve, reject);
} else {
resolve(thenable);
}
} else {
resolve(thenable);
}
} catch(error) {
reject(error);
}
});
return promise;
}
export { resolve };
...@@ -520,4 +520,286 @@ describe("RSVP extensions", function() { ...@@ -520,4 +520,286 @@ describe("RSVP extensions", function() {
}); });
}); });
}); });
describe("RSVP.resolve", function(){
specify("it should exist", function(){
assert(RSVP.resolve);
});
describe("1. If x is a promise, adopt its state ", function(){
specify("1.1 If x is pending, promise must remain pending until x is fulfilled or rejected.", function(done){
var expectedValue, resolver, thenable, wrapped;
expectedValue = 'the value';
thenable = {
then: function(resolve, reject){
resolver = resolve;
}
};
wrapped = RSVP.resolve(thenable);
wrapped.then(function(value){
assert(value === expectedValue);
done();
})
resolver(expectedValue);
});
specify("1.2 If/when x is fulfilled, fulfill promise with the same value.", function(done){
var expectedValue, thenable, wrapped;
expectedValue = 'the value';
thenable = {
then: function(resolve, reject){
resolve(expectedValue);
}
};
wrapped = RSVP.resolve(thenable);
wrapped.then(function(value){
assert(value === expectedValue);
done();
})
});
specify("1.3 If/when x is rejected, reject promise with the same reason.", function(done){
var expectedError, thenable, wrapped;
expectedError = new Error();
thenable = {
then: function(resolve, reject){
reject(expectedError);
}
};
wrapped = RSVP.resolve(thenable);
wrapped.then(null, function(error){
assert(error === expectedError);
done();
});
});
});
describe("2. Otherwise, if x is an object or function,", function(){
specify("2.1 Let then x.then", function(done){
var accessCount, resolver, wrapped, thenable;
accessCount = 0;
thenable = { };
Object.defineProperty(thenable, 'then', {
get: function(){
accessCount++;
if (accessCount > 1) {
throw new Error();
}
return function(){ };
}
});
assert(accessCount === 0);
wrapped = RSVP.resolve(thenable);
assert(accessCount === 1);
done();
});
specify("2.2 If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.", function(done){
var wrapped, thenable, expectedError;
expectedError = new Error();
thenable = { };
Object.defineProperty(thenable, 'then', {
get: function(){
throw expectedError;
}
});
wrapped = RSVP.resolve(thenable);
wrapped.then(null, function(error){
assert(error === expectedError, 'incorrect exception was thrown');
done();
});
});
describe('2.3. If then is a function, call it with x as this, first argument resolvePromise, and second argument rejectPromise, where', function(){
specify('2.3.1 If/when resolvePromise is called with a value y, run Resolve(promise, y)', function(done){
var expectedSuccess, resolver, rejector, thenable, wrapped, calledThis;
thenable = {
then: function(resolve, reject){
calledThis = this;
resolver = resolve;
rejector = reject;
}
};
expectedSuccess = 'success';
wrapped = RSVP.resolve(thenable);
wrapped.then(function(success){
assert(calledThis === thenable, 'this must be the thenable');
assert(success === expectedSuccess, 'rejected promise with x');
done();
});
resolver(expectedSuccess);
});
specify('2.3.2 If/when rejectPromise is called with a reason r, reject promise with r.', function(done){
var expectedError, resolver, rejector, thenable, wrapped, calledThis,
thenable = {
then: function(resolve, reject){
calledThis = this;
resolver = resolve;
rejector = reject;
}
};
expectedError = new Error();
wrapped = RSVP.resolve(thenable);
wrapped.then(null, function(error){
assert(error === expectedError, 'rejected promise with x');
done();
});
rejector(expectedError);
});
specify("2.3.3 If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored", function(done){
var expectedError, expectedSuccess, resolver, rejector, thenable, wrapped, calledThis,
calledRejected, calledResolved;
calledRejected = 0;
calledResolved = 0;
thenable = {
then: function(resolve, reject){
calledThis = this;
resolver = resolve;
rejector = reject;
}
};
expectedError = new Error();
wrapped = RSVP.resolve(thenable);
wrapped.then(function(){
calledResolved++;
}, function(error){
calledRejected++;
assert(calledResolved === 0, 'never resolved');
assert(calledRejected === 1, 'rejected only once');
assert(error === expectedError, 'rejected promise with x');
});
rejector(expectedError);
rejector(expectedError);
rejector('foo');
resolver('bar');
resolver('baz');
setTimeout(function(){
assert(calledRejected === 1, 'only rejected once');
assert(calledResolved === 0, 'never resolved');
done();
}, 50);
});
describe("2.3.4 If calling then throws an exception e", function(){
specify("2.3.4.1 If resolvePromise or rejectPromise have been called, ignore it.", function(done){
var expectedSuccess, resolver, rejector, thenable, wrapped, calledThis,
calledRejected, calledResolved;
expectedSuccess = 'success';
thenable = {
then: function(resolve, reject){
resolve(expectedSuccess);
throw expectedError;
}
};
wrapped = RSVP.resolve(thenable);
wrapped.then(function(success){
assert(success === expectedSuccess, 'resolved not errored');
done();
});
});
specify("2.3.4.2 Otherwise, reject promise with e as the reason.", function(done) {
var expectedError, resolver, rejector, thenable, wrapped, calledThis, callCount;
expectedError = new Error();
callCount = 0;
thenable = { then: function() { throw expectedError; } };
wrapped = RSVP.resolve(thenable);
wrapped.then(null, function(error){
callCount++;
assert(expectedError === error, 'expected the correct error to be rejected');
done();
});
assert(callCount === 0, 'expected async, was sync');
});
});
});
specify("2.4 If then is not a function, fulfill promise with x", function(done){
var expectedError, resolver, rejector, thenable, wrapped, calledThis, callCount;
thenable = { then: 3 };
callCount = 0;
wrapped = RSVP.resolve(thenable);
wrapped.then(function(success){
callCount++;
assert(thenable === success, 'fulfilled promise with x');
done();
});
assert(callCount === 0, 'expected async, was sync');
});
});
describe("3. If x is not an object or function, ", function(){
specify("fulfill promise with x.", function(done){
var thenable, callCount, wrapped;
thenable = null;
callCount = 0;
wrapped = RSVP.resolve(thenable);
wrapped.then(function(success){
callCount++;
assert(success === thenable, 'fulfilled promise with x');
done();
}, function(a){
assert(false, 'should not also reject');
});
assert(callCount === 0, 'expected async, was sync');
});
});
});
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment