/** * 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.Get; import static com.github.anba.es6draft.runtime.AbstractOperations.IsCallable; import static com.github.anba.es6draft.runtime.AbstractOperations.IsConstructor; import static com.github.anba.es6draft.runtime.internal.Errors.newTypeError; import static com.github.anba.es6draft.runtime.types.Undefined.UNDEFINED; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import com.github.anba.es6draft.runtime.ExecutionContext; import com.github.anba.es6draft.runtime.Realm; import com.github.anba.es6draft.runtime.Task; import com.github.anba.es6draft.runtime.internal.CompatibilityOption; import com.github.anba.es6draft.runtime.internal.Messages; import com.github.anba.es6draft.runtime.internal.MutRef; import com.github.anba.es6draft.runtime.internal.ObjectAllocator; import com.github.anba.es6draft.runtime.internal.ScriptException; 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.Undefined; 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.1 Promise Abstract Operations * <li>25.4.2 Promise Tasks * </ul> */ public final class PromiseAbstractOperations { private PromiseAbstractOperations() { } /** * Returns the Promise object allocator for the realm object. * * @param realm * the realm instance * @return the promise allocator */ public static ObjectAllocator<? extends PromiseObject> GetPromiseAllocator(Realm realm) { if (realm.isEnabled(CompatibilityOption.PromiseRejection)) { return FinalizablePromiseObject::new; } return PromiseObject::new; } public static final class ResolvingFunctions { /** [[Resolve]] */ private final PromiseResolveFunction resolve; /** [[Reject]] */ private final PromiseRejectFunction reject; ResolvingFunctions(PromiseResolveFunction resolve, PromiseRejectFunction reject) { this.resolve = resolve; this.reject = reject; } /** * [[Resolve]] * * @return the resolve function */ public PromiseResolveFunction getResolve() { return resolve; } /** * [[Reject]] * * @return the reject function */ public PromiseRejectFunction getReject() { return reject; } } /** * <h2>25.4.1 Promise Abstract Operations</h2> * <p> * 25.4.1.3 CreateResolvingFunctions ( promise ) * * @param cx * the execution context * @param promise * the promise object * @return the resolving functions tuple */ public static ResolvingFunctions CreateResolvingFunctions(ExecutionContext cx, PromiseObject promise) { /* step 1 */ AtomicBoolean alreadyResolved = new AtomicBoolean(false); /* steps 2-4 */ PromiseResolveFunction resolve = new PromiseResolveFunction(cx.getRealm(), promise, alreadyResolved); /* steps 5-7 */ PromiseRejectFunction reject = new PromiseRejectFunction(cx.getRealm(), promise, alreadyResolved); /* step 8 */ return new ResolvingFunctions(resolve, reject); } /** * 25.4.1.3.1 Promise Reject Functions */ public static final class PromiseRejectFunction extends BuiltinFunction { /** [[Promise]] */ private final PromiseObject promise; /** [[AlreadyResolved]] */ private final AtomicBoolean alreadyResolved; public PromiseRejectFunction(Realm realm, PromiseObject promise, AtomicBoolean alreadyResolved) { this(realm, promise, alreadyResolved, null); createDefaultFunctionProperties(); } private PromiseRejectFunction(Realm realm, PromiseObject promise, AtomicBoolean alreadyResolved, Void ignore) { super(realm, ANONYMOUS, 1); this.promise = promise; this.alreadyResolved = alreadyResolved; } @Override public PromiseRejectFunction clone() { return new PromiseRejectFunction(getRealm(), promise, alreadyResolved, null); } @Override public Undefined call(ExecutionContext callerContext, Object thisValue, Object... args) { ExecutionContext calleeContext = calleeContext(); Object reason = argument(args, 0); /* step 1 (not applicable) */ /* step 2 */ PromiseObject promise = this.promise; /* steps 3-5 */ if (!alreadyResolved.compareAndSet(false, true)) { return UNDEFINED; } /* step 6 */ RejectPromise(calleeContext, promise, reason); return UNDEFINED; } } /** * 25.4.1.3.2 Promise Resolve Functions */ public static final class PromiseResolveFunction extends BuiltinFunction { /** [[Promise]] */ private final PromiseObject promise; /** [[AlreadyResolved]] */ private final AtomicBoolean alreadyResolved; public PromiseResolveFunction(Realm realm, PromiseObject promise, AtomicBoolean alreadyResolved) { this(realm, promise, alreadyResolved, null); createDefaultFunctionProperties(); } private PromiseResolveFunction(Realm realm, PromiseObject promise, AtomicBoolean alreadyResolved, Void ignore) { super(realm, ANONYMOUS, 1); this.promise = promise; this.alreadyResolved = alreadyResolved; } @Override public PromiseResolveFunction clone() { return new PromiseResolveFunction(getRealm(), promise, alreadyResolved, null); } @Override public Undefined call(ExecutionContext callerContext, Object thisValue, Object... args) { ExecutionContext calleeContext = calleeContext(); Object resolution = argument(args, 0); /* step 1 (not applicable) */ /* step 2 */ PromiseObject promise = this.promise; /* steps 3-5 */ if (!alreadyResolved.compareAndSet(false, true)) { return UNDEFINED; } /* step 6 */ if (resolution == promise) { // SameValue ScriptException selfResolutionError = newTypeError(calleeContext, Messages.Key.PromiseSelfResolution); RejectPromise(calleeContext, promise, selfResolutionError.getValue()); return UNDEFINED; } /* step 7 */ if (!Type.isObject(resolution)) { FulfillPromise(calleeContext, promise, resolution); return UNDEFINED; } /* steps 8-10 */ Object then; try { then = Get(calleeContext, Type.objectValue(resolution), "then"); } catch (ScriptException e) { /* step 9 */ RejectPromise(calleeContext, promise, e.getValue()); return UNDEFINED; } /* step 11 */ if (!IsCallable(then)) { FulfillPromise(calleeContext, promise, resolution); return UNDEFINED; } /* step 12 */ Realm realm = calleeContext.getRealm(); realm.enqueuePromiseTask(new PromiseResolveThenableTask(realm, promise, Type .objectValue(resolution), (Callable) then)); /* step 13 */ return UNDEFINED; } } /** * <h2>25.4.1 Promise Abstract Operations</h2> * <p> * 25.4.1.4 FulfillPromise (promise, value) * * @param cx * the execution context * @param promise * the promise object * @param value * the resolve value */ public static void FulfillPromise(ExecutionContext cx, PromiseObject promise, Object value) { /* steps 1-6 */ List<PromiseReaction> reactions = promise.fufill(value); /* step 7 */ TriggerPromiseReactions(cx, reactions, value); } /** * <h2>25.4.1 Promise Abstract Operations</h2> * <p> * 25.4.1.5 NewPromiseCapability ( C ) * * @param cx * the execution context * @param c * the promise constructor function * @return the new promise capability record */ public static PromiseCapability<ScriptObject> NewPromiseCapability(ExecutionContext cx, Object c) { /* step 1 */ if (!IsConstructor(c)) { throw newTypeError(cx, Messages.Key.NotConstructor); } /* steps 2-11 */ return NewPromiseCapability(cx, (Constructor) c); } /** * <h2>25.4.1 Promise Abstract Operations</h2> * <p> * 25.4.1.5 NewPromiseCapability ( C ) * * @param cx * the execution context * @param c * the promise constructor function * @return the new promise capability record */ public static PromiseCapability<ScriptObject> NewPromiseCapability(ExecutionContext cx, Constructor c) { /* steps 1-2 (not applicable) */ /* step 3 (moved) */ /* steps 4-5 */ GetCapabilitiesExecutor executor = new GetCapabilitiesExecutor(cx.getRealm()); /* steps 6-7 */ ScriptObject promise = c.construct(cx, c, executor); /* step 8 */ Object resolve = executor.resolve.get(); if (!IsCallable(resolve)) { throw newTypeError(cx, Messages.Key.NotCallable); } /* step 9 */ Object reject = executor.reject.get(); if (!IsCallable(reject)) { throw newTypeError(cx, Messages.Key.NotCallable); } /* steps 3, 10-11 */ return new PromiseCapability<>(promise, (Callable) resolve, (Callable) reject); } /** * 25.4.1.5.1 GetCapabilitiesExecutor Functions */ public static final class GetCapabilitiesExecutor extends BuiltinFunction { /** [[Resolve]] */ private final MutRef<Object> resolve; /** [[Reject]] */ private final MutRef<Object> reject; public GetCapabilitiesExecutor(Realm realm) { this(realm, new MutRef<>(UNDEFINED), new MutRef<>(UNDEFINED)); createDefaultFunctionProperties(); } private GetCapabilitiesExecutor(Realm realm, MutRef<Object> resolve, MutRef<Object> reject) { super(realm, ANONYMOUS, 2); this.resolve = resolve; this.reject = reject; } @Override public GetCapabilitiesExecutor clone() { return new GetCapabilitiesExecutor(getRealm(), resolve, reject); } @Override public Undefined call(ExecutionContext callerContext, Object thisValue, Object... args) { ExecutionContext calleeContext = calleeContext(); Object resolve = argument(args, 0); Object reject = argument(args, 1); /* step 1 (not applicable) */ /* step 2 (omitted) */ /* step 3 */ if (!Type.isUndefined(this.resolve.get())) { throw newTypeError(calleeContext, Messages.Key.NotUndefined); } /* step 4 */ if (!Type.isUndefined(this.reject.get())) { throw newTypeError(calleeContext, Messages.Key.NotUndefined); } /* step 5 */ this.resolve.set(resolve); /* step 6 */ this.reject.set(reject); /* step 7 */ return UNDEFINED; } } /** * <h2>25.4.1 Promise Abstract Operations</h2> * <p> * 25.4.1.6 IsPromise ( x ) * * @param x * the object * @return {@code true} if <var>x</var> is an initialized promise object */ public static boolean IsPromise(Object x) { /* steps 1-3 */ return x instanceof PromiseObject; } /** * <h2>25.4.1 Promise Abstract Operations</h2> * <p> * 25.4.1.7 RejectPromise (promise, reason) * * @param cx * the execution context * @param promise * the promise object * @param reason * the rejection reason */ public static void RejectPromise(ExecutionContext cx, PromiseObject promise, Object reason) { /* steps 1-6 */ List<PromiseReaction> reactions = promise.reject(reason); /* step 7 */ TriggerPromiseReactions(cx, reactions, reason); } /** * <h2>25.4.1 Promise Abstract Operations</h2> * <p> * 25.4.1.8 TriggerPromiseReactions ( reactions, argument ) * * @param cx * the execution context * @param reactions * the list of promise reactions * @param argument * the reaction task argument */ public static void TriggerPromiseReactions(ExecutionContext cx, List<PromiseReaction> reactions, Object argument) { /* step 1 */ Realm realm = cx.getRealm(); for (PromiseReaction reaction : reactions) { realm.enqueuePromiseTask(new PromiseReactionTask(realm, reaction, argument)); } /* step 2 (return) */ } /** * <h2>25.4.2 Promise Jobs</h2> * <p> * 25.4.2.1 PromiseReactionJob( reaction, argument ) */ public static final class PromiseReactionTask implements Task { private final Realm realm; private final PromiseReaction reaction; private final Object argument; public PromiseReactionTask(Realm realm, PromiseReaction reaction, Object argument) { this.realm = realm; this.reaction = reaction; this.argument = argument; } @Override public void execute() { ExecutionContext cx = realm.defaultContext(); /* step 1 (not applicable) */ /* step 2 */ PromiseCapability<?> promiseCapability = reaction.getCapabilities(); /* steps 3-7 */ if (reaction.getType() == PromiseReaction.Type.Identity) { /* steps 4, 8 */ promiseCapability.getResolve().call(cx, UNDEFINED, argument); } else if (reaction.getType() == PromiseReaction.Type.Thrower) { /* steps 5, 7 */ promiseCapability.getReject().call(cx, UNDEFINED, argument); } else { /* step 3 */ Callable handler = reaction.getHandler(); /* steps 6-7 */ Object handlerResult; try { handlerResult = handler.call(cx, UNDEFINED, argument); } catch (ScriptException e) { /* step 7 */ promiseCapability.getReject().call(cx, UNDEFINED, e.getValue()); return; } /* steps 8-9 */ promiseCapability.getResolve().call(cx, UNDEFINED, handlerResult); } } } /** * <h2>25.4.2 Promise Jobs</h2> * <p> * 25.4.2.2 PromiseResolveThenableJob ( promiseToResolve, thenable, then ) */ public static final class PromiseResolveThenableTask implements Task { private final Realm realm; private final PromiseObject promise; private final ScriptObject thenable; private final Callable then; public PromiseResolveThenableTask(Realm realm, PromiseObject promise, ScriptObject thenable, Callable then) { this.realm = realm; this.promise = promise; this.thenable = thenable; this.then = then; } @Override public void execute() { ExecutionContext cx = realm.defaultContext(); /* step 1 */ ResolvingFunctions resolvingFunctions = CreateResolvingFunctions(cx, promise); /* steps 2-4 */ try { /* step 2 */ then.call(cx, thenable, resolvingFunctions.getResolve(), resolvingFunctions.getReject()); } catch (ScriptException e) { /* step 3 */ resolvingFunctions.getReject().call(cx, UNDEFINED, e.getValue()); } } } /** * PromiseBuiltinCapability () * * @param cx * the execution context * @return the promise capability record */ @SuppressWarnings("unchecked") public static PromiseCapability<PromiseObject> PromiseBuiltinCapability(ExecutionContext cx) { PromiseConstructor constructor = (PromiseConstructor) cx.getIntrinsic(Intrinsics.Promise); PromiseCapability<ScriptObject> capability = NewPromiseCapability(cx, constructor); assert capability.getPromise() instanceof PromiseObject; return (PromiseCapability<PromiseObject>) (PromiseCapability<?>) capability; } /** * PromiseOf (value) * * @param cx * the execution context * @param value * the resolved value * @return the new promise object */ public static PromiseObject PromiseOf(ExecutionContext cx, Object value) { /* steps 1-2 */ PromiseCapability<PromiseObject> capability = PromiseBuiltinCapability(cx); /* steps 3-4 */ capability.getResolve().call(cx, UNDEFINED, value); /* step 5 */ return capability.getPromise(); } /** * PromiseOf (value) * * @param cx * the execution context * @param e * the exception object * @return the new promise object */ public static PromiseObject PromiseOf(ExecutionContext cx, ScriptException e) { /* steps 1-2 */ PromiseCapability<PromiseObject> capability = PromiseBuiltinCapability(cx); /* steps 3-4 */ capability.getReject().call(cx, UNDEFINED, e.getValue()); /* step 5 */ return capability.getPromise(); } }