/**
* Copyright (c) 2012-2016 André Bargull
* Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms.
*
* <https://github.com/anba/es6draft>
*/
package com.github.anba.es6draft.runtime.objects.promise;
import static com.github.anba.es6draft.runtime.AbstractOperations.*;
import static com.github.anba.es6draft.runtime.internal.Errors.newTypeError;
import static com.github.anba.es6draft.runtime.internal.Properties.createProperties;
import static com.github.anba.es6draft.runtime.objects.promise.PromiseAbstractOperations.CreateResolvingFunctions;
import static com.github.anba.es6draft.runtime.objects.promise.PromiseAbstractOperations.GetPromiseAllocator;
import static com.github.anba.es6draft.runtime.objects.promise.PromiseAbstractOperations.IsPromise;
import static com.github.anba.es6draft.runtime.objects.promise.PromiseAbstractOperations.NewPromiseCapability;
import static com.github.anba.es6draft.runtime.objects.promise.PromiseCapability.IfAbruptRejectPromise;
import static com.github.anba.es6draft.runtime.types.Undefined.UNDEFINED;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import com.github.anba.es6draft.runtime.ExecutionContext;
import com.github.anba.es6draft.runtime.Realm;
import com.github.anba.es6draft.runtime.internal.Initializable;
import com.github.anba.es6draft.runtime.internal.Messages;
import com.github.anba.es6draft.runtime.internal.Properties.Accessor;
import com.github.anba.es6draft.runtime.internal.Properties.Attributes;
import com.github.anba.es6draft.runtime.internal.Properties.Function;
import com.github.anba.es6draft.runtime.internal.Properties.Prototype;
import com.github.anba.es6draft.runtime.internal.Properties.Value;
import com.github.anba.es6draft.runtime.internal.ScriptException;
import com.github.anba.es6draft.runtime.internal.ScriptIterator;
import com.github.anba.es6draft.runtime.objects.promise.PromiseAbstractOperations.ResolvingFunctions;
import com.github.anba.es6draft.runtime.types.BuiltinSymbol;
import com.github.anba.es6draft.runtime.types.Callable;
import com.github.anba.es6draft.runtime.types.Constructor;
import com.github.anba.es6draft.runtime.types.Intrinsics;
import com.github.anba.es6draft.runtime.types.ScriptObject;
import com.github.anba.es6draft.runtime.types.Type;
import com.github.anba.es6draft.runtime.types.builtins.ArrayObject;
import com.github.anba.es6draft.runtime.types.builtins.BuiltinConstructor;
import com.github.anba.es6draft.runtime.types.builtins.BuiltinFunction;
/**
* <h1>25 Control Abstraction Objects</h1><br>
* <h2>25.4 Promise Objects</h2>
* <ul>
* <li>25.4.3 The Promise Constructor
* <li>25.4.4 Properties of the Promise Constructor
* </ul>
*/
public final class PromiseConstructor extends BuiltinConstructor implements Initializable {
/**
* Constructs a new Promise constructor function.
*
* @param realm
* the realm object
*/
public PromiseConstructor(Realm realm) {
super(realm, "Promise", 1);
}
@Override
public void initialize(Realm realm) {
createProperties(realm, this, Properties.class);
}
@Override
public PromiseConstructor clone() {
return new PromiseConstructor(getRealm());
}
/**
* 25.4.3.1 Promise ( executor )
*/
@Override
public Object call(ExecutionContext callerContext, Object thisValue, Object... args) {
/* step 1 */
throw newTypeError(calleeContext(), Messages.Key.InvalidCall, "Promise");
}
/**
* 25.4.3.1 Promise ( executor )
*/
@Override
public PromiseObject construct(ExecutionContext callerContext, Constructor newTarget,
Object... args) {
ExecutionContext calleeContext = calleeContext();
Object executor = argument(args, 0);
/* step 1 (not applicable) */
/* step 2 */
if (!IsCallable(executor)) {
throw newTypeError(calleeContext, Messages.Key.NotCallable);
}
/* steps 3-7 */
PromiseObject promise = OrdinaryCreateFromConstructor(calleeContext, newTarget,
Intrinsics.PromisePrototype, GetPromiseAllocator(calleeContext.getRealm()));
/* step 8 */
ResolvingFunctions resolvingFunctions = CreateResolvingFunctions(calleeContext, promise);
/* steps 9-10 */
try {
/* step 9 */
((Callable) executor).call(calleeContext, UNDEFINED, resolvingFunctions.getResolve(),
resolvingFunctions.getReject());
} catch (ScriptException e) {
/* step 10 */
resolvingFunctions.getReject().call(calleeContext, UNDEFINED, e.getValue());
}
/* step 11 */
return promise;
}
/**
* 25.4.4 Properties of the Promise Constructor
*/
public enum Properties {
;
@Prototype
public static final Intrinsics __proto__ = Intrinsics.FunctionPrototype;
@Value(name = "length", attributes = @Attributes(writable = false, enumerable = false,
configurable = true))
public static final int length = 1;
@Value(name = "name", attributes = @Attributes(writable = false, enumerable = false,
configurable = true))
public static final String name = "Promise";
/**
* 25.4.4.2 Promise.prototype
*/
@Value(name = "prototype", attributes = @Attributes(writable = false, enumerable = false,
configurable = false))
public static final Intrinsics prototype = Intrinsics.PromisePrototype;
/**
* 25.4.4.1 Promise.all ( iterable )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param iterable
* the iterable
* @return the promise object
*/
@Function(name = "all", arity = 1)
public static Object all(ExecutionContext cx, Object thisValue, Object iterable) {
/* steps 1-5 */
if (!Type.isObject(thisValue)) {
throw newTypeError(cx, Messages.Key.NotObjectType);
}
ScriptObject c = Type.objectValue(thisValue);
/* steps 6-7 */
PromiseCapability<?> promiseCapability = NewPromiseCapability(cx, c);
/* step 8 */
ScriptIterator<?> iterator;
try {
iterator = GetScriptIterator(cx, iterable);
} catch (ScriptException e) {
/* step 9 */
return IfAbruptRejectPromise(cx, e, promiseCapability);
}
/* steps 10-13 */
try {
return PerformPromiseAll(cx, iterator, c, promiseCapability);
} catch (ScriptException e) {
/* step 12 */
try {
iterator.close(e);
} catch (ScriptException inner) {
return IfAbruptRejectPromise(cx, inner, promiseCapability);
}
return IfAbruptRejectPromise(cx, e, promiseCapability);
}
}
/**
* 25.4.4.3 Promise.race ( iterable )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param iterable
* the iterable
* @return the promise object
*/
@Function(name = "race", arity = 1)
public static Object race(ExecutionContext cx, Object thisValue, Object iterable) {
/* steps 1-5 */
if (!Type.isObject(thisValue)) {
throw newTypeError(cx, Messages.Key.NotObjectType);
}
ScriptObject c = Type.objectValue(thisValue);
/* steps 6-7 */
PromiseCapability<?> promiseCapability = NewPromiseCapability(cx, c);
/* step 8 */
ScriptIterator<?> iterator;
try {
iterator = GetScriptIterator(cx, iterable);
} catch (ScriptException e) {
/* step 9 */
return IfAbruptRejectPromise(cx, e, promiseCapability);
}
/* steps 10-13 */
try {
return PerformPromiseRace(cx, iterator, c, promiseCapability);
} catch (ScriptException e) {
/* step 12 */
try {
iterator.close(e);
} catch (ScriptException inner) {
return IfAbruptRejectPromise(cx, inner, promiseCapability);
}
return IfAbruptRejectPromise(cx, e, promiseCapability);
}
}
/**
* 25.4.4.4 Promise.reject ( r )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param r
* the rejected value
* @return the new promise object
*/
@Function(name = "reject", arity = 1)
public static Object reject(ExecutionContext cx, Object thisValue, Object r) {
/* steps 1-2 */
if (!Type.isObject(thisValue)) {
throw newTypeError(cx, Messages.Key.NotObjectType);
}
/* steps 3-4 */
PromiseCapability<?> promiseCapability = NewPromiseCapability(cx, thisValue);
/* steps 5-6 */
promiseCapability.getReject().call(cx, UNDEFINED, r);
/* step 7 */
return promiseCapability.getPromise();
}
/**
* 25.4.4.5 Promise.resolve ( x )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param x
* the resolved value
* @return the new promise object
*/
@Function(name = "resolve", arity = 1)
public static Object resolve(ExecutionContext cx, Object thisValue, Object x) {
/* steps 1-2 */
if (!Type.isObject(thisValue)) {
throw newTypeError(cx, Messages.Key.NotObjectType);
}
/* step 3 */
if (IsPromise(x)) {
Object constructor = Get(cx, (PromiseObject) x, "constructor");
if (constructor == thisValue) { // SameValue
return x;
}
}
/* steps 4-5 */
PromiseCapability<?> promiseCapability = NewPromiseCapability(cx, thisValue);
/* steps 6-7 */
promiseCapability.getResolve().call(cx, UNDEFINED, x);
/* step 8 */
return promiseCapability.getPromise();
}
/**
* 25.4.4.6 get Promise [ @@species ]
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the species object
*/
@Accessor(name = "get [Symbol.species]", symbol = BuiltinSymbol.species,
type = Accessor.Type.Getter)
public static Object species(ExecutionContext cx, Object thisValue) {
/* step 1 */
return thisValue;
}
}
/**
* 25.4.4.1.1 Runtime Semantics: PerformPromiseAll( iteratorRecord, constructor,
* resultCapability)
*
* @param <PROMISE>
* the promise type
* @param cx
* the execution context
* @param iterator
* the iterator object
* @param constructor
* the constructor object
* @param resultCapability
* the new promise capability record
* @return the new promise object
*/
public static <PROMISE extends ScriptObject> PROMISE PerformPromiseAll(ExecutionContext cx,
ScriptIterator<?> iterator, ScriptObject constructor, PromiseCapability<PROMISE> resultCapability) {
/* steps 1-2 (not applicable) */
/* step 3 */
ArrayList<Object> values = new ArrayList<>();
/* step 4 */
AtomicInteger remainingElementsCount = new AtomicInteger(1);
/* step 5 */
int index = 0;
/* step 6 */
while (iterator.hasNext()) {
/* steps 6.a-c, 6.e-g */
Object nextValue = iterator.next();
/* step 6.h */
// Using 'null' instead of undefined to be able to verify that no values are overwritten
values.add(null);
/* steps 6.i-j */
Object nextPromise = Invoke(cx, constructor, "resolve", nextValue);
/* steps 6.k-p */
PromiseAllResolveElementFunction resolveElement = new PromiseAllResolveElementFunction(cx.getRealm(),
new AtomicBoolean(false), index, values, resultCapability, remainingElementsCount);
/* step 6.q */
remainingElementsCount.incrementAndGet();
/* steps 6.r-s */
Invoke(cx, nextPromise, "then", resolveElement, resultCapability.getReject());
/* step 6.t */
index += 1;
}
/* step 6.d */
if (remainingElementsCount.decrementAndGet() == 0) {
ArrayObject valuesArray = CreateArrayFromList(cx, values);
resultCapability.getResolve().call(cx, UNDEFINED, valuesArray);
}
return resultCapability.getPromise();
}
/**
* 25.4.4.1.2 Promise.all Resolve Element Functions
*/
public static final class PromiseAllResolveElementFunction extends BuiltinFunction {
/** [[AlreadyCalled]] */
private final AtomicBoolean alreadyCalled;
/** [[Index]] */
private final int index;
/** [[Values]] */
private final ArrayList<Object> values;
/** [[Capabilities]] */
private final PromiseCapability<?> capabilities;
/** [[RemainingElements]] */
private final AtomicInteger remainingElements;
public PromiseAllResolveElementFunction(Realm realm, AtomicBoolean alreadyCalled,
int index, ArrayList<Object> values, PromiseCapability<?> capabilities,
AtomicInteger remainingElements) {
this(realm, alreadyCalled, index, values, capabilities, remainingElements, null);
createDefaultFunctionProperties();
}
private PromiseAllResolveElementFunction(Realm realm, AtomicBoolean alreadyCalled,
int index, ArrayList<Object> values, PromiseCapability<?> capabilities,
AtomicInteger remainingElements, Void ignore) {
super(realm, ANONYMOUS, 1);
this.alreadyCalled = alreadyCalled;
this.index = index;
this.values = values;
this.capabilities = capabilities;
this.remainingElements = remainingElements;
}
@Override
public PromiseAllResolveElementFunction clone() {
return new PromiseAllResolveElementFunction(getRealm(), alreadyCalled, index, values,
capabilities, remainingElements, null);
}
@Override
public Object call(ExecutionContext callerContext, Object thisValue, Object... args) {
ExecutionContext calleeContext = calleeContext();
Object x = argument(args, 0);
/* steps 1-3 */
if (!alreadyCalled.compareAndSet(false, true)) {
return UNDEFINED;
}
/* step 4 */
int index = this.index;
/* step 5 */
ArrayList<Object> values = this.values;
/* step 6 */
PromiseCapability<?> promiseCapability = this.capabilities;
/* step 7 */
AtomicInteger remainingElementsCount = this.remainingElements;
/* step 8 */
assert values.get(index) == null : String.format("values[%d] = %s", index,
values.get(index));
values.set(index, x);
/* steps 9-10 */
if (remainingElementsCount.decrementAndGet() == 0) {
ArrayObject valuesArray = CreateArrayFromList(calleeContext, values);
return promiseCapability.getResolve().call(calleeContext, UNDEFINED, valuesArray);
}
/* step 11 */
return UNDEFINED;
}
}
/**
* 25.4.4.3.1 Runtime Semantics: PerformPromiseRace ( iteratorRecord, promiseCapability, C )
*
* @param <PROMISE>
* the promise type
* @param cx
* the execution context
* @param iterator
* the iterator object
* @param constructor
* the constructor object
* @param promiseCapability
* the new promise capability record
* @return the new promise object
*/
public static <PROMISE extends ScriptObject> PROMISE PerformPromiseRace(ExecutionContext cx,
ScriptIterator<?> iterator, ScriptObject constructor, PromiseCapability<PROMISE> promiseCapability) {
/* step 1 */
while (iterator.hasNext()) {
/* steps 1.a-c, 1.e-g */
Object nextValue = iterator.next();
/* steps 1.f-g */
Object nextPromise = Invoke(cx, constructor, "resolve", nextValue);
/* steps 1.h-i */
Invoke(cx, nextPromise, "then", promiseCapability.getResolve(), promiseCapability.getReject());
}
/* step 1.d */
return promiseCapability.getPromise();
}
}