Skip to content

Commit

Permalink
Replace valueOf by inspect. Closes kriskowal#256.
Browse files Browse the repository at this point in the history
`valueOf` is left deprecated.
  • Loading branch information
domenic committed May 18, 2013
1 parent 9ab54a1 commit 0ecdaed
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 73 deletions.
120 changes: 74 additions & 46 deletions q.js
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,17 @@ function captureLine() {
}
}

function deprecate(callback, name, alternative) {
return function () {
if (typeof console !== "undefined" &&
typeof console.warn === "function") {
console.warn(name + " is deprecated, use " + alternative +
" instead.", new Error("").stack);
}
return callback.apply(callback, arguments);
};
}

// end of shims
// beginning of real work

Expand Down Expand Up @@ -452,15 +463,23 @@ function defer() {
}
};

promise.valueOf = function () {
// XXX deprecated
promise.valueOf = deprecate(function () {
if (messages) {
return promise;
}
var nearer = valueOf(resolvedPromise);
if (isPromise(nearer)) {
resolvedPromise = nearer; // shorten chain
var nearerValue = nearer(resolvedPromise);
if (isPromise(nearerValue)) {
resolvedPromise = nearerValue; // shorten chain
}
return nearer;
return nearerValue;
}, "valueOf", "inspect");

promise.inspect = function () {
if (!resolvedPromise) {
return { state: "pending" };
}
return resolvedPromise.inspect();
};

if (Q.longStackJumpLimit > 0 && hasStacks) {
Expand Down Expand Up @@ -584,7 +603,7 @@ function promise(resolver) {
* bought and sold.
*/
Q.makePromise = makePromise;
function makePromise(descriptor, fallback, valueOf, exception, isException) {
function makePromise(descriptor, fallback, inspect) {
if (fallback === void 0) {
fallback = function (op) {
return reject(new Error(
Expand All @@ -611,12 +630,23 @@ function makePromise(descriptor, fallback, valueOf, exception, isException) {
}
};

if (valueOf) {
promise.valueOf = valueOf;
}
promise.inspect = inspect;

// XXX deprecated `valueOf` and `exception` support
if (inspect) {
var inspected = inspect();
if (inspected.state === "rejected") {
promise.exception = inspected.reason;
}

if (isException) {
promise.exception = exception;
promise.valueOf = deprecate(function () {
var inspected = inspect();
if (inspected.state === "pending" ||
inspected.state === "rejected") {
return promise;
}
return inspected.value;
});
}

return promise;
Expand Down Expand Up @@ -680,10 +710,15 @@ makePromise.prototype.toString = function () {
* @param object
* @returns most resolved (nearest) form of the object
*/
Q.nearer = valueOf;
function valueOf(value) {

// XXX should we re-do this?
Q.nearer = nearer;
function nearer(value) {
if (isPromise(value)) {
return value.valueOf();
var inspected = value.inspect();
if (inspected.state === "fulfilled") {
return inspected.value;
}
}
return value;
}
Expand All @@ -708,7 +743,7 @@ function isPromiseAlike(object) {
*/
Q.isPending = isPending;
function isPending(object) {
return !isFulfilled(object) && !isRejected(object);
return isPromise(object) && object.inspect().state === "pending";
}

/**
Expand All @@ -717,16 +752,15 @@ function isPending(object) {
*/
Q.isFulfilled = isFulfilled;
function isFulfilled(object) {
return !isPromiseAlike(valueOf(object));
return !isPromise(object) || object.inspect().state === "fulfilled";
}

/**
* @returns whether the given object is a rejected promise.
*/
Q.isRejected = isRejected;
function isRejected(object) {
object = valueOf(object);
return isPromise(object) && "exception" in object;
return isPromise(object) && object.inspect().state === "rejected";
}

// This promise library consumes exceptions thrown in handlers so they can be
Expand Down Expand Up @@ -792,9 +826,9 @@ function reject(reason) {
}
}, function fallback() {
return this;
}, function valueOf() {
return this;
}, reason, true);
}, function inspect() {
return { state: "rejected", reason: reason };
});

// Note that the reason has not been handled.
displayUnhandledReasons();
Expand All @@ -809,37 +843,37 @@ function reject(reason) {
* @param value immediate reference
*/
Q.fulfill = fulfill;
function fulfill(object) {
function fulfill(value) {
return makePromise({
"when": function () {
return object;
return value;
},
"get": function (name) {
return object[name];
return value[name];
},
"set": function (name, value) {
object[name] = value;
"set": function (name, rhs) {
value[name] = rhs;
},
"delete": function (name) {
delete object[name];
delete value[name];
},
"post": function (name, args) {
// Mark Miller proposes that post with no name should apply a
// promised function.
if (name === null || name === void 0) {
return object.apply(void 0, args);
return value.apply(void 0, args);
} else {
return object[name].apply(object, args);
return value[name].apply(value, args);
}
},
"apply": function (thisP, args) {
return object.apply(thisP, args);
return value.apply(thisP, args);
},
"keys": function () {
return object_keys(object);
return object_keys(value);
}
}, void 0, function valueOf() {
return object;
}, void 0, function inspect() {
return { state: "fulfilled", value: value };
});
}

Expand All @@ -856,16 +890,8 @@ function resolve(value) {
if (isPromise(value)) {
return value;
}
// In order to break infinite recursion or loops between `then` and
// `resolve`, it is necessary to attempt to extract fulfilled values
// out of foreign promise implementations before attempting to wrap
// them as unresolved promises. It is my hope that other
// implementations will implement `valueOf` to synchronously extract
// the fulfillment value from their fulfilled promises. If the
// other promise library does not implement `valueOf`, the
// implementations on primordial prototypes are harmless.
value = valueOf(value);
// assimilate thenables, CommonJS/Promises/A+

// assimilate thenables
if (isPromiseAlike(value)) {
return coerce(value);
} else {
Expand Down Expand Up @@ -906,7 +932,7 @@ function master(object) {
}, function fallback(op, args) {
return dispatch(object, op, args);
}, function () {
return valueOf(object);
return resolve(object).inspect();
});
}

Expand Down Expand Up @@ -1281,8 +1307,10 @@ function all(promises) {
var countDown = 0;
var deferred = defer();
array_reduce(promises, function (undefined, promise, index) {
if (isFulfilled(promise)) {
promises[index] = valueOf(promise);
var snapshot;
if (isPromise(promise) &&
(snapshot = promise.inspect()).state === "fulfilled") {
promises[index] = snapshot.value;
} else {
++countDown;
when(promise, function (value) {
Expand Down
63 changes: 36 additions & 27 deletions spec/q-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -785,49 +785,58 @@ describe("promises for functions", function () {

});

describe("valueOf", function () {
describe("inspect", function () {

it("of fulfillment", function () {
expect(Q.resolve(10).valueOf()).toEqual(10);
it("for a fulfilled promise", function () {
expect(Q.resolve(10).inspect()).toEqual({
state: "fulfilled",
value: 10
});
});

it("of rejection", function () {
it("for a rejected promise", function () {
var error = new Error("In your face.");
var rejection = Q.reject(error);
expect(rejection.valueOf()).toBe(rejection);
expect(rejection.valueOf().exception).toBe(error);
var rejected = Q.reject(error);
expect(rejected.inspect()).toEqual({
state: "rejected",
reason: error
});
});

it("of deferred", function () {
var deferred = Q.defer();
expect(deferred.promise.valueOf()).toBe(deferred.promise);
it("for a pending, unresolved promise", function () {
var pending = Q.defer().promise;
expect(pending.inspect()).toEqual({ state: "pending" });
});

it("of deferred rejection", function () {
it("for a promise resolved to a rejected promise", function () {
var deferred = Q.defer();
var error = new Error("Rejected!");
var rejection = Q.reject(error);
deferred.resolve(rejection);
expect(deferred.promise.valueOf()).toBe(rejection);
expect(deferred.promise.valueOf().exception).toBe(error);
var rejected = Q.reject(error);
deferred.resolve(rejected);

expect(deferred.promise.inspect()).toEqual({
state: "rejected",
reason: error
});
});

it("of deferred fulfillment", function () {
it("for a promise resolved to a fulfilled promise", function () {
var deferred = Q.defer();
deferred.fulfill(10);
expect(deferred.promise.valueOf()).toBe(10);
var fulfilled = Q.resolve(10);
deferred.resolve(fulfilled);

expect(deferred.promise.inspect()).toEqual({
state: "fulfilled",
value: 10
});
});

it("of deferred deferred", function () {
it("for a promise resolved to a pending promise", function () {
var a = Q.defer();
var b = Q.defer();
a.resolve(b.promise);
expect(a.promise.valueOf()).toBe(b.promise);
});

it("should not convert `Date` instances to milliseconds", function () {
var promise = Q.resolve(new Date(2012, 10, 4));
expect(promise.valueOf()).toEqual(new Date(2012, 10, 4));
expect(a.promise.inspect()).toEqual({ state: "pending" });
});

});
Expand Down Expand Up @@ -928,7 +937,7 @@ describe("promise states", function () {
expect(childPromise.isPending()).toBe(false);
expect(childPromise.isRejected()).toBe(false);
expect(childPromise.isFulfilled()).toBe(true);
expect(childPromise.valueOf()).toBe(2);
expect(childPromise.inspect().value).toBe(2);
});
});

Expand Down Expand Up @@ -1166,8 +1175,8 @@ describe("allResolved", function () {
expect(Q.isFulfilled(promises[1])).toBe(true);
expect(Q.isRejected(promises[2])).toBe(true);

expect(promises[0].valueOf()).toEqual(1);
expect(promises[1].valueOf()).toEqual(2);
expect(promises[0].inspect().value).toEqual(1);
expect(promises[1].inspect().value).toEqual(2);
});
});

Expand Down

0 comments on commit 0ecdaed

Please sign in to comment.