This repository is meant to fully flesh out a subset of the "AP2" promise consensus developed over the last month on es-discuss. In particular, it provides the subset the DOM needs as soon as possible, omitting flatMap
and accept
for now but building a conceptual foundation that would allow them to be added at a later date.
It is meant to succeed the current DOM Promises spec, and fixes a number of bugs in that spec while also changing some of the exposed APIs and behavior to make it more forward-compatible with the full AP2 consensus.
A promise carries several internal data properties:
[[IsPromise]]
: all promises are branded with this property, and no other objects are. Uninitialized promises have it set toundefined
, whereas initialized ones have it set totrue
.[[Following]]
: either unset, or a promise thatp
is following.[[Value]]
: either unset, or promise's direct fulfillment value (derived by resolving it with a non-thenable).[[Reason]]
: either unset, or a promise's direct rejection reason (derived by rejecting it).[[Derived]]
: a list, initially empty, of derived promise transforms that need to be processed once the promise's[[Value]]
or[[Reason]]
are set.[[PromiseConstructor]]
: the function object that was used to construct this promise. Used for branding checks inPromise.cast
.
To successfully and consistently assimilate thenable objects into real promises, an implementation must maintain a weak map of thenables to promises. Notably, both the keys and values must be weakly stored. Since this weak map is not directly exposed, it does not need to be a true ECMAScript weak map, with the accompanying prototype and such. However, we refer to it using ECMAScript notation in this spec, i.e.:
ThenableCoercions.has(thenable)
ThenableCoercions.get(thenable)
ThenableCoercions.set(thenable, promise)
The Derived Promise Transform type is used to encapsulate promises which are derived from a given promise, optionally including fulfillment or rejection handlers that will be used to transform the derived promise relative to the originating promise. They are stored in a promise's [[Derived]]
internal data property until the promise's [[Value]]
or [[Reason]]
are set, at which time changes propagate to all derived promise transforms in the list and the list is cleared.
Derived promise transforms are Records composed of three named fields:
[[DerivedPromise]]
: the derived promise in need of updating.[[OnFulfilled]]
: the fulfillment handler to be used as a transformation, if the originating promise becomes fulfilled.[[OnRejected]]
: the rejection handler to be used as a transformation, if the originating promise becomes rejected.
The operator IsPromise
checks for the promise brand on an object.
- Return
true
ifIsObject(x)
andx.[[IsPromise]]
istrue
. - Otherwise, return
false
.
The operator ToPromise
coerces its argument to a promise, ensuring it is of the specified constructor C
, or returns the argument if it is already a promise matching that constructor.
- If
IsPromise(x)
andSameValue(x.[[PromiseConstructor]], C)
istrue
, returnx
. - Otherwise,
- Let
deferred
beGetDeferred(C)
. - Call
deferred.[[Resolve]](x)
. - Return
deferred.[[Promise]]
.
- Let
The operator Resolve
resolves a promise with a value.
- If
p.[[Following]]
,p.[[Value]]
, orp.[[Reason]]
are set, terminate these steps. - If
IsPromise(x)
,- If
SameValue(p, x)
,- Let
selfResolutionError
be a newly-createdTypeError
object. - Call
SetReason(p, selfResolutionError)
.
- Let
- Otherwise, if
x.[[Following]]
is set,- Let
p.[[Following]]
bex.[[Following]]
. - Add
{ [[DerivedPromise]]: p, [[OnFulfilled]]: undefined, [[OnRejected]]: undefined }
tox.[[Following]].[[Derived]]
.
- Let
- Otherwise, if
x.[[Value]]
is set, callSetValue(p, x.[[Value]])
. - Otherwise, if
x.[[Reason]]
is set, callSetReason(p, x.[[Reason]])
. - Otherwise,
- Let
p.[[Following]]
bex
. - Add
{ [[DerivedPromise]]: p, [[OnFulfilled]]: undefined, [[OnRejected]]: undefined }
tox.[[Derived]]
.
- Let
- If
- Otherwise, call
SetValue(p, x)
.
The operator Reject
rejects a promise with a reason.
- If
p.[[Following]]
,p.[[Value]]
, orp.[[Reason]]
are set, terminate these steps. - Call
SetReason(p, r)
.
The operator Then
queues up fulfillment and/or rejection handlers on a promise for when it becomes fulfilled or rejected, or schedules them to be called in the next microtask if the promise is already fulfilled or rejected. It returns a derived promise, transformed by the passed handlers.
- If
p.[[Following]]
is set,- Return
Then(p.[[Following]], onFulfilled, onRejected)
.
- Return
- Otherwise,
- Let
C
beGet(p, "constructor")
. - If retrieving the property throws an exception
e
,- Let
q
be a newly-created promise object. - Call
Reject(q, e)
.
- Let
- Otherwise,
- Let
q
beGetDeferred(C).[[Promise]]
. - Let
derived
be{ [[DerivedPromise]]: q, [[OnFulfilled]]: onFulfilled, [[OnRejected]]: onRejected }
. - Call
UpdateDerivedFromPromise(derived, p)
.
- Let
- Return
q
.
- Let
The operator PropagateToDerived
propagates a promise's [[Value]]
or [[Reason]]
to all of its derived promises.
- Assert: exactly one of
p.[[Value]]
orp.[[Reason]]
is set. - For each derived promise transform
derived
inp.[[Derived]]
,- Call
UpdateDerived(derived, p)
.
- Call
- Clear
p.[[Derived]]
.
Note: step 3 is not strictly necessary, as preconditions prevent p.[[Derived]]
from ever being used again after this point.
The operator UpdateDerived
propagates a promise's state to a single derived promise using any relevant transforms.
- Assert: exactly one of
originator.[[Value]]
ororiginator.[[Reason]]
is set. - If
originator.[[Value]]
is set,- If
IsObject(originator.[[Value]])
, queue a microtask to run the following:- If
ThenableCoercions.has(originator.[[Value]])
,- Let
coercedAlready
beThenableCoercions.get(originator.[[Value]])
. - Call
UpdateDerivedFromPromise(derived, coercedAlready)
.
- Let
- Otherwise,
- Let
then
beGet(originator.[[Value]], "then")
. - If retrieving the property throws an exception
e
, callUpdateDerivedFromReason(derived, e)
. - Otherwise, if
IsCallable(then)
,- Let
coerced
beCoerceThenable(originator.[[Value]], then)
. - Call
UpdateDerivedFromPromise(derived, coerced)
.
- Let
- Otherwise, call
UpdateDerivedFromValue(derived, originator.[[Value]])
.
- Let
- If
- Otherwise, call
UpdateDerivedFromValue(derived, originator.[[Value]])
.
- If
- Otherwise, call
UpdateDerivedFromReason(derived, originator.[[Reason]])
.
The operator UpdateDerivedFromValue
propagates a value to a derived promise, using the relevant onFulfilled
transform if it is callable.
- If
IsCallable(derived.[[OnFulfilled]])
, callCallHandler(derived.[[DerivedPromise]], derived.[[OnFulfilled]], value)
. - Otherwise, call
SetValue(derived.[[DerivedPromise]], value)
.
The operator UpdateDerivedFromReason
propagates a reason to a derived promise, using the relevant onRejected
transform if it is callable.
- If
IsCallable(derived.[[OnRejected]])
, callCallHandler(derived.[[DerivedPromise]], derived.[[OnRejected]], reason)
. - Otherwise, call
SetReason(derived.[[DerivedPromise]], reason)
.
The operator UpdateDerivedFromPromise
propagates one promise's state to the derived promise, using the relevant transform if it is callable.
- If
promise.[[Value]]
orpromise.[[Reason]]
is set, callUpdateDerived(derived, promise)
. - Otherwise, add
derived
topromise.[[Derived]]
.
The operator CallHandler
applies a transformation to a value or reason and uses it to update a derived promise.
- Queue a microtask to do the following:
- Let
v
behandler.[[Call]](undefined, (argument))
. - If calling the function throws an exception
e
, callReject(derivedPromise, e)
. - Otherwise, call
Resolve(derivedPromise, v)
.
- Let
The operator SetValue
encapsulates the process of setting a promise's value and then propagating this to any derived promises.
- Assert: neither
p.[[Value]]
norp.[[Reason]]
are set. - Set
p.[[Value]]
tovalue
. - Unset
p.[[Following]]
. - Call
PropagateToDerived(p)
.
Note: step 3 is not strictly necessary, as all code paths check p.[[Value]]
before using p.[[Following]]
.
The operator SetReason
encapsulates the process of setting a promise's reason and then propagating this to any derived promises.
- Assert: neither
p.[[Value]]
norp.[[Reason]]
are set. - Set
p.[[Reason]]
toreason
. - Unset
p.[[Following]]
. - Call
PropagateToDerived(p)
.
Note: step 3 is not strictly necessary, as all code paths check p.[[Reason]]
before using p.[[Following]]
.
The operator CoerceThenable
takes a "thenable" object whose then
method has been extracted and creates a promise from it. It memoizes its results so as to avoid getting inconsistent answers in the face of ill-behaved thenables; the memoized results are later checked by UpdateDerived
.
- Assert:
IsObject(thenable)
. - Assert:
IsCallable(then)
. - Assert: the execution context stack is empty.
- Let
p
be a newly-created promise object. - Let
resolve(x)
be an ECMAScript function that callsResolve(p, x)
. - Let
reject(r)
be an ECMAScript function that callsReject(p, r)
. - Call
then.[[Call]](thenable, (resolve, reject))
. - If calling the function throws an exception
e
, callReject(p, e)
. - Call
ThenableCoercions.set(thenable, p)
. - Return
p
.
The operator GetDeferred
takes a potential constructor function, and attempts to use that constructor function in the fashion of the normal promise constructor to extract resolve and reject functions, returning the constructed promise along with those two functions controlling its state. This is useful to support subclassing, as this operation is generic on any constructor that calls a passed resolver argument in the same way as the Promise
constructor. We use it to generalize static methods of Promise
to any subclass.
- If
IsConstructor(C)
,- Let
resolver
be an ECMAScript function that:- Lets
resolve
be the valueresolver
is passed as its first argument. - Lets
reject
be the valueresolver
is passed as its second argument.
- Lets
- Let
promise
beC.[[Construct]]((resolver))
.
- Let
- Otherwise,
- Let
promise
be a newly-created promise object. - Let
resolve(x)
be an ECMAScript function that callsResolve(promise, x)
. - Let
reject(r)
be an ECMAScript function that callsReject(promise, r)
.
- Let
- Return the record
{ [[Promise]]: promise, [[Resolve]]: resolve, [[Reject]]: reject }
.
The Promise
constructor is the %Promise%
intrinsic object and the initial value of the Promise
property of the global object. When Promise
is called as a function rather than as a constructor, it initiializes its this
value with the internal state necessary to support the Promise.prototype
internal methods.
The Promise
constructor is designed to be subclassable. It may be used as the value of an extends
clause of a class declaration. Subclass constructors that intended to inherit the specified Promise
behavior must include a super
call to the Promise
constructor to initialize the [[IsPromise]]
state of subclass instances.
When Promise
is called with the argument resolver
, the following steps are taken. If being called to initialize an uninitialized promise object created by Promise[@@create]
, resolver
is assumed to be a function and is given the two arguments resolve
and reject
which will perform their eponymous operations on the promise.
- Let
promise
be thethis
value. - If
Type(promise)
is notObject
, throw aTypeError
exception. - If
promise.[[IsPromise]]
is unset, then throw aTypeError
exception. - If
promise.[[IsPromise]]
is notundefined
, then throw aTypeError
exception. - If not
IsCallable(resolver)
, throw aTypeError
exception. - Set
promise.[[IsPromise]]
totrue
. - Let
resolve(x)
be an ECMAScript function that callsResolve(promise, x)
. - Let
reject(r)
be an ECMAScript function that callsReject(promise, r)
. - Call
resolver.[[Call]](undefined, (resolve, reject))
. - If calling the function throws an exception
e
, callReject(promise, e)
. - Return
promise
.
Promise
called as part of a new
expression with argument list argumentList
simply delegates to the usual ECMAScript spec mechanisms for creating new objects, triggering the initialization subsequence of the above Promise(resolver)
procedure.
- Return
OrdinaryConstruct(Promise, argumentsList)
.
Promise[@@create]()
allocates a new uninitialized promise object, installing the unforgable brand [[IsPromise]]
on the promise.
- Let
p
beOrdinaryCreateFromConstructor(this, "%PromisePrototype%", ([[IsPromise]]))
. - Set
p.[[PromiseConstructor]]
tothis
. - Return
p
.
Promise.resolve
returns a new promise resolved with the passed argument.
- Let
deferred
beGetDeferred(this)
. - Call
deferred.[[Resolve]](x)
. - Return
deferred.[[Promise]]
.
Promise.reject
returns a new promise rejected with the passed argument.
- Let
deferred
beGetDeferred(this)
. - Call
deferred.[[Reject]](r)
. - Return
deferred.[[Promise]]
.
Promise.cast
coerces its argument to a promise, or returns the argument if it is already a promise.
- Return
ToPromise(this, x)
.
Promise.race
returns a new promise which is settled in the same way as the first passed promise to settle. It casts all elements of the passed iterable to promises before running this algorithm.
- Let
deferred
beGetDeferred(this)
. - For each value
nextValue
ofiterable
,- Let
nextPromise
beToPromise(this, nextValue)
. - Call
Then(nextPromise, deferred.[[Resolve]], deferred.[[Reject]])
.
- Let
- Return
deferred.[[Promise]]
.
Promise.all
returns a new promise which is fulfilled with an array of fulfillment values for the passed promises, or rejects with the reason of the first passed promise that rejects. It casts all elements of the passed iterable to promises before running this algorithm.
- Let
deferred
beGetDeferred(this)
. - Let
values
beArrayCreate(0)
. - Let
countdown
be0
. - Let
index
be0
. - For each value
nextValue
ofiterable
,- Let
currentIndex
be the current value ofindex
. - Let
nextPromise
beToPromise(this, nextValue)
. - Let
onFulfilled(v)
be an ECMAScript function that:- Calls
values.[[DefineOwnProperty]](currentIndex, { [[Value]]: v, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }
. - Lets
countdown
becountdown - 1
. - If
countdown
is0
, callsdeferred.[[Resolve]](values)
.
- Calls
- Call
Then(nextPromise, onFulfilled, deferred.[[Reject]])
. - Let
index
beindex + 1
. - Let
countdown
becountdown + 1
.
- Let
- If
index
is0
,- Call
deferred.[[Resolve]](values)
.
- Call
- Return
deferred.[[Promise]]
.
The Promise
prototype object is itself an ordinary object. It is not a Promise
instance and does not have a [[IsPromise]]
internal data property.
The value of the [[Prototype]]
internal data property of the Promise
prototype object is the standard built-in Object
prototype object.
The methods of the Promise
prototype object are not generic and the this
value passed to them must be an object that has a [[IsPromise]]
internal data property that has been initialized to true
.
The intrinsic object %PromisePrototype%
is the initial value of the "prototype"
data property of the intrinsic %Promise%
.
The initial value of Promise.prototype.constructor
is the built-in Promise
constructor.
- If
IsPromise(this)
isfalse
, throw aTypeError
. - Otherwise, return
Then(this, onFulfilled, onRejected)
.
- Return
Invoke(this, "then", (undefined, onRejected))
.