Skip to content

Commit

Permalink
Fix Promise.cast; make Promise.all/race more generic.
Browse files Browse the repository at this point in the history
`Promise.cast` was temporarily referencing a nonexistent ToPromise as a result of the refactor. I inlined the previous text into its definition.

Then, inside `Promise.all` and `Promise.race`, I now use Invoke to invoke the "cast" method of the called-on constructor, and to invoke the "then" method of the returned-from-cast promise. This latter step closes #53.
  • Loading branch information
domenic committed Oct 28, 2013
1 parent f7d537e commit 422d131
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 44 deletions.
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,14 +252,14 @@ This property has the attributes { [[Writable]]: **false**, [[Enumerable]]: **fa
1. Return _deferred_.[[Promise]].
1. Let _nextValue_ be the result of calling IteratorValue(_next_).
1. RejectIfAbrupt(_nextValue_, _deferred_).
1. Let _nextPromise_ be the result of calling ToPromise(_C_, _nextValue_).
1. Let _nextPromise_ be the result of calling Invoke(_C_, `"cast"`, (_nextValue_)).
1. RejectIfAbrupt(_nextPromise_, _deferred_).
1. Let _currentIndex_ be the current value of _index_.
1. Let `onFulfilled(v)` be an ECMAScript function that:
1. Calls the [[DefineOwnProperty]] internal method of _values_ with arguments _currentIndex_ and Property Descriptor { [[Value]]: _v_, [[Writable]]: **true**, [[Enumerable]]: **true**, [[Configurable]]: **true** }.
1. Sets _countdown_ to _countdown_ - 1.
1. If _countdown_ is 0, calls `resolve.[[Call]](undefined, (values))`.
1. Let _result_ be the result of calling Then(_nextPromise_, _onFulfilled_, _deferred_.[[Reject]]).
1. If _countdown_ is 0, calls `Call(deferred.[[Resolve]], values)`.
1. Let _result_ be the result of calling Invoke(_nextPromise_, `"then"`, (_onFulfilled_, _deferred_.[[Reject]])).
1. RejectIfAbrupt(_result_, _deferred_).
1. Set _index_ to _index_ + 1.
1. Set _countdown_ to _countdown_ + 1.
Expand All @@ -271,7 +271,13 @@ Note: The `all` function is an intentionally generic utility method; it does not
`cast` coerces its argument to a promise, or returns the argument if it is already a promise.

1. Let _C_ be the **this** value.
1. Return the result of calling ToPromise(_C_, _x_).
1. If IsPromise(_x_) is **true**,
1. Let _constructor_ be the value of _x_'s [[PromiseConstructor]] internal slot.
1. If SameValue(_constructor_, _C_) is **true**, return _x_.
1. Let _deferred_ be the result of calling GetDeferred(_C_).
1. ReturnIfAbrupt(_deferred_).
1. Call(_deferred_.[[Resolve]], _x_).
1. Return _deferred_.[[Promise]].

Note: The `cast` function is an intentionally generic utility method; it does not require that its **this** value be the Promise constructor. Therefore, it can be transferred to or inherited by any other constructors that may be called with a single function argument.

Expand All @@ -290,9 +296,9 @@ Note: The `cast` function is an intentionally generic utility method; it does no
1. If _next_ is **false**, return _deferred_.[[Promise]].
1. Let _nextValue_ be the result of calling IteratorValue(_next_).
1. RejectIfAbrupt(_nextValue_, _deferred_).
1. Let _nextPromise_ be the result of calling ToPromise(_C_, _nextValue_).
1. Let _nextPromise_ be the result of calling Invoke(_C_, `"cast"`, (_nextValue_)).
1. RejectIfAbrupt(_nextPromise_, _deferred_).
1. Let _result_ be the result of calling Then(_nextPromise_, _deferred_.[[Resolve]], _deferred_.[[Reject]]).
1. Let _result_ be the result of calling Invoke(_nextPromise_, `"then"`, (_deferred_.[[Resolve]], _deferred_.[[Reject]])).
1. RejectIfAbrupt(_result_, _deferred_).

Note: The `race` function is an intentionally generic utility method; it does not require that its **this** value be the Promise constructor. Therefore, it can be transferred to or inherited by any other constructors that may be called with a single function argument.
Expand Down
2 changes: 1 addition & 1 deletion run-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ describe.skip("Memoization of thenables", function () {

// In Node v0.11.7 --harmony, arrays are not iterable, but they have `.values()` methods that return iterables.
// In real life we should just use arrays and not need to do the silly `.values()` thing.
describe.skip("Promise.all", function () {
describe("Promise.all", function () {
it("fulfills if passed an empty array", function (done) {
adapter.done(Promise.all([].values()), function (value) {
assert(Array.isArray(value));
Expand Down
80 changes: 43 additions & 37 deletions testable-implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,37 +297,6 @@ Object.defineProperty(Promise, "@@create", {
configurable: true
});

define_method(Promise, "resolve", function (x) {
let C = this;
let deferred = GetDeferred(C);
Call(deferred["[[Resolve]]"], x);
return deferred["[[Promise]]"];
});

define_method(Promise, "reject", function (r) {
let C = this;
let deferred = GetDeferred(C);
Call(deferred["[[Reject]]"], r);
return deferred["[[Promise]]"];
});

define_method(Promise, "cast", function (x) {
let C = this;
return ToPromise(C, x);
});

define_method(Promise, "race", function (iterable) {
let C = this;
let deferred = GetDeferred(C);

for (let nextValue of iterable) {
let nextPromise = ToPromise(C, nextValue);
Then(nextPromise, deferred["[[Resolve]]"], deferred["[[Reject]]"]);
}

return deferred["[[Promise]]"];
});

define_method(Promise, "all", function (iterable) {
let C = this;
let deferred = GetDeferred(C);
Expand All @@ -336,10 +305,8 @@ define_method(Promise, "all", function (iterable) {
let countdown = 0;
let index = 0;

let resolve = deferred["[[Resolve]]"];

for (let nextValue of iterable) {
let nextPromise = ToPromise(C, nextValue);
let nextPromise = C.cast(nextValue);
let currentIndex = index;

let onFulfilled = function (v) {
Expand All @@ -351,18 +318,57 @@ define_method(Promise, "all", function (iterable) {
});
countdown = countdown - 1;
if (countdown === 0) {
resolve.call(undefined, values);
Call(deferred["[[Resolve]]"], values);
}
};

Then(nextPromise, onFulfilled, deferred["[[Reject]]"]);
nextPromise.then(onFulfilled, deferred["[[Reject]]"]);

index = index + 1;
countdown = countdown + 1;
}

if (index === 0) {
resolve.call(undefined, values);
Call(deferred["[[Resolve]]"], values);
}

return deferred["[[Promise]]"];
});

define_method(Promise, "resolve", function (x) {
let C = this;
let deferred = GetDeferred(C);
Call(deferred["[[Resolve]]"], x);
return deferred["[[Promise]]"];
});

define_method(Promise, "reject", function (r) {
let C = this;
let deferred = GetDeferred(C);
Call(deferred["[[Reject]]"], r);
return deferred["[[Promise]]"];
});

define_method(Promise, "cast", function (x) {
let C = this;
if (IsPromise(x) === true) {
let constructor = get_slot(x, "[[PromiseConstructor]]");
if (SameValue(constructor, C) === true) {
return x;
}
}
let deferred = GetDeferred(C);
Call(deferred["[[Resolve]]"], x);
return deferred["[[Promise]]"];
});

define_method(Promise, "race", function (iterable) {
let C = this;
let deferred = GetDeferred(C);

for (let nextValue of iterable) {
let nextPromise = C.cast(nextValue);
nextPromise.then(deferred["[[Resolve]]"], deferred["[[Reject]]"]);
}

return deferred["[[Promise]]"];
Expand Down

0 comments on commit 422d131

Please sign in to comment.