Skip to content

Commit

Permalink
Applied planned backward-incompatible changes to defined, `isResolv…
Browse files Browse the repository at this point in the history
…ed`, `post`, `Promise`.
  • Loading branch information
kriskowal committed Mar 1, 2011
1 parent f577193 commit 7eb729b
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 77 deletions.
39 changes: 22 additions & 17 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
# vim:ts=4:sts=4:sw=4:et:tw=60

Deprecations:
- I plan in the next backward-incompatible revision to move
the `defined` method from `q` to `q/util`. Accordingly,
please begin using the version of `defined` exported
from the latter module.
- the `Promise` constructor, as such, is deprecated in
favor of the name `makePromise`, as conceived proper by
Mark Miller in a style where TitleCase is reserved for
constructors.
- In the next backward-incompatible revision, "post" will
be reverted to its original non-variadic behavior,
receiving the post body as a single argument, albeit an
array. An "invoke" method will be introduced to provide
the variadic convenience.
- The `isResolved` and `isRejected` functions will be
re-arranged as `isFulfilled`, and `isRejected`, since
`isResolved` means either fullfilled or rejected.
Next major release: BACKWARD-INCOMPATIBLE
- The `post` method has been reverted to its original
signature, as provided in Tyler Close's `ref_send` API.
That is, `post` accepts two arguments, the second of
which is an arbitrary object, but usually invocation
arguments as an `Array`. To provide variadic arguments
to `post`, there is a new `invoke` function that posts
the variadic arguments to the value given in the first
argument.
- The `defined` method has been moved from `q` to `q/util`
since it gets no use in practice but is still
theoretically useful.
- The `Promise` constructor has been renamed to
`makePromise` to be consistent with the convention that
functions that do not require the `new` keyword to be
used as constructors have camelCase names.
- The `isResolved` function has been renamed to
`isFulfilled`. There is a new `isResolved` function that
indicates whether a value is not a promise or, if it is a
promise, whether it has been either fulfilled or
rejected. The code has been revised to reflect this
nuance in terminology.

Next minor release:
- Exceptions thrown in the callbacks of a `when` call are
Expand Down
121 changes: 67 additions & 54 deletions lib/q.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,6 @@ function defer() {
* bought and sold.
*/
exports.makePromise = Promise;
exports.Promise = Promise; // deprecated

function Promise(descriptor, fallback, valueOf) {

if (fallback === undefined) {
Expand Down Expand Up @@ -184,8 +182,8 @@ function Promise(descriptor, fallback, valueOf) {
};

// provide thenables, CommonJS/Promises/A
Promise.prototype.then = function (resolved, rejected) {
return when(this, resolved, rejected);
Promise.prototype.then = function (fulfilled, rejected) {
return when(this, fulfilled, rejected);
};

Promise.prototype.toSource = function () {
Expand All @@ -200,27 +198,36 @@ freeze(Promise.prototype);

/**
* @returns whether the given object is a promise.
* Otherwise it is a resolved value.
* Otherwise it is a fulfilled value.
*/
exports.isPromise = isPromise;
function isPromise(object) {
return object && typeof object[DUCK] === "function";
};

/**
* @returns whether the given object is a fully
* resolved value.
* @returns whether the given object is a resolved promise.
*/
exports.isResolved = isResolved;
function isResolved(object) {
if (object === undefined || object === null)
return true;
return !isRejected(object) && !isPromise(object.valueOf());
return !isPromise(object.valueOf());
};

/**
* @returns whether the given object is a
* rejected promise.
* @returns whether the given object is a value or fulfilled
* promise.
*/
exports.isFulfilled = isFulfilled;
function isFulfilled(object) {
if (object === undefined || object === null)
return true;
return !isPromise(object.valueOf()) && !isRejected(object);
};

/**
* @returns whether the given object is a rejected promise.
*/
exports.isRejected = isRejected;
function isRejected(object) {
Expand Down Expand Up @@ -299,16 +306,15 @@ function ref(object) {
return reject("Cannot delete property " + name + " of " + object);
return delete object[name];
},
"post": function (name /*...args*/) {
var args = Array.prototype.slice.call(arguments, 1);
"post": function (name, value) {
if (object === undefined || object === null)
return reject("" + object + " has no methods");
var method = object[name];
if (!method)
return reject("No such method " + name + " on object " + object);
if (!method.apply)
return reject("Property " + name + " on object " + object + " is not a method");
return object[name].apply(object, args);
return object[name].apply(object, value);
},
"keys": function () {
return Object.keys(object);
Expand Down Expand Up @@ -344,25 +350,25 @@ function def(object) {
*
* Guarantees:
*
* 1. that resolved and rejected will be called only once.
* 2. that either the resolved callback or the rejected callback will be
* 1. that fulfilled and rejected will be called only once.
* 2. that either the fulfilled callback or the rejected callback will be
* called, but not both.
* 3. that resolved and rejected will not be called in this turn.
* 3. that fulfilled and rejected will not be called in this turn.
*
* @param value promise or immediate reference to observe
* @param resolve function to be called with the resolved value
* @param fulfilled function to be called with the fulfilled value
* @param rejected function to be called with the rejection reason
* @return promise for the return value from the invoked callback
*/
exports.when = when;
function when(value, resolved, rejected) {
function when(value, fulfilled, rejected) {
var deferred = defer();
var done = false; // ensure the untrusted promise makes at most a
// single call to one of the callbacks

function _resolved(value) {
function _fulfilled(value) {
try {
return resolved ? resolved(value) : value;
return fulfilled ? fulfilled(value) : value;
} catch (exception) {
if (typeof process !== "undefined") {
process.emit('uncaughtException', exception);
Expand All @@ -386,7 +392,7 @@ function when(value, resolved, rejected) {
if (done)
return;
done = true;
deferred.resolve(ref(value)[DUCK]("when", _resolved, _rejected));
deferred.resolve(ref(value)[DUCK]("when", _fulfilled, _rejected));
}, function (reason) {
if (done)
return;
Expand All @@ -397,31 +403,32 @@ function when(value, resolved, rejected) {
}

/**
* Like "when", but attempts to return a fully resolved
* value in the same turn. If the given value is fully
* resolved, and the value returned by the resolved
* callback is fully resolved, asap returns the latter
* value in the same turn. Otherwise, it returns a promise
* that will be resolved in a future turn.
* Like "when", but attempts to return a fulfilled value in
* the same turn. If the given value is fulfilled and the
* value returned by the fulfilled callback is fulfilled,
* asap returns the latter value in the same turn.
* Otherwise, it returns a promise that will be resolved in
* a future turn.
*
* This method is an experiment in providing an API
* that can unify synchronous and asynchronous API's.
* An API that uses "asap" guarantees that, if it
* is provided fully resolved values, it would produce
* fully resolved values, but if it is provided
* asynchronous promises, it will produce asynchronous
* promises.
* This method is an experiment in providing an API that can
* unify synchronous and asynchronous API's. An API that
* uses "asap" guarantees that, if it is provided fully
* resolved values, it will produce fully resolved values or
* throw exceptions, but if it is provided asynchronous
* promises, it will produce asynchronous promises.
*
* /!\ WARNING: this method is experimental and likely
* to be removed on the grounds that it probably
* will result in composition hazards.
* /!\ WARNING: this method is experimental and likely to be
* removed on the grounds that it probably will result in
* composition hazards.
*/
exports.asap = function (value, resolved, rejected) {
resolved = resolved || identity;
if (isResolved(value))
return resolved(value.valueOf()).valueOf();
exports.asap = function (value, fulfilled, rejected) {
fulfilled = fulfilled || identity;
if (isFulfilled(value))
return fulfilled(value.valueOf()).valueOf();
else if (isRejected(value))
throw value.valueOf().reason;
else
return when(value, resolved, rejected);
return when(value, fulfilled, rejected);
};

/**
Expand Down Expand Up @@ -482,14 +489,31 @@ exports.put = Method("put");
*/
exports.del = Method("del");

/**
* Invokes a method in a future turn.
* @param object promise or immediate reference for target object
* @param name name of method to invoke
* @param value a value to post, typically an array of
* invocation arguments for promises that
* are ultimately backed with `ref` values,
* as opposed to those backed with URLs
* wherein the posted value can be any
* JSON serializable object.
* @return promise for the return value
*/
var post = exports.post = Method("post");

/**
* Invokes a method in a future turn.
* @param object promise or immediate reference for target object
* @param name name of method to invoke
* @param ...args array of invocation arguments
* @return promise for the return value
*/
exports.post = Method("post");
exports.invoke = function (value, name) {
var args = Array.prototype.slice.call(arguments, 2);
return post(value, name, args);
};

/**
* Requests the names of the owned properties of a promised
Expand All @@ -499,17 +523,6 @@ exports.post = Method("post");
*/
exports.keys = Method("keys");

/**
* Guarantees that the give promise resolves to a defined, non-null value.
*/
exports.defined = function (value) {
return exports.when(value, function (value) {
if (value === undefined || value === null)
return reject("Resolved undefined value: " + value);
return value;
});
};

/**
* Throws an error with the given reason.
*/
Expand Down
25 changes: 23 additions & 2 deletions test/methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,28 @@ exports['test post resolved'] = function (ASSERT, done) {
}
};

Q.when(Q.post(d.promise, 'a', 1), function (result) {
Q.when(Q.post(d.promise, 'a', [1]), function (result) {
ASSERT.ok(result === 2, 'correct value is returned by post');
ASSERT.ok(value._a === 1, 'post invoked function as expected');
done();
}, function (reason) {
ASSERT.fail(reason);
done();
});
d.resolve(value);
};

exports['test invoke resolved'] = function (ASSERT, done) {
var d = Q.defer();
var value = {
_a: null,
a: function a(value) {
this._a = value;
return 1 + value;
}
};

Q.when(Q.invoke(d.promise, 'a', 1), function (result) {
ASSERT.ok(result === 2, 'correct value is returned by post');
ASSERT.ok(value._a === 1, 'post invoked function as expected');
done();
Expand All @@ -77,7 +98,7 @@ exports['test post on undefined method'] = function (ASSERT, done) {
var d = Q.defer();
var value = {};

Q.when(Q.post(d.promise, 'a', 1), function (result) {
Q.when(Q.post(d.promise, 'a', [1]), function (result) {
ASSERT.fail('Unxpeced to call non-existing method:' + result);
done();
}, function (reason) {
Expand Down
4 changes: 2 additions & 2 deletions test/promised-chains.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ exports['test resolution propagates through chain'] = function(assert, done) {
( Q.when
( deferred.promise
, function(value) {
resoved = true
resolved = true
return value
}
, reject
)
, function(value) {
assert.equal(value, resolution, 'value resoved as expected')
assert.equal(value, resolution, 'value resolved as expected')
assert.ok(nextTurn, 'callback is called in next turn of event loop')
done()
}
Expand Down
5 changes: 3 additions & 2 deletions test/reject.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@

var Q = require('q');

exports['test reject: isRejected, isResolved'] = function (ASSERT, done) {
exports['test reject: isRejected, isResolved, isFulfilled'] = function (ASSERT, done) {
var future = false;
var reason = {};
var rejection = Q.reject(reason);
ASSERT.ok(Q.isRejected(rejection), 'should be rejected in current turn');
ASSERT.ok(!Q.isResolved(rejection), 'should not be resolved in current turn');
ASSERT.ok(!Q.isFulfilled(rejection), 'should not be fulfilled in current turn');
ASSERT.ok(Q.isResolved(rejection), 'should be resolved in current turn');
Q.when(function () {
ASSERT.ok(false, 'should not be resolved in a future turn');
done();
Expand Down

0 comments on commit 7eb729b

Please sign in to comment.