![JAR search and dependency download from the Maven repository](/logo.png)
org.mozilla.javascript.NativePromise Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rhino-runtime Show documentation
Show all versions of rhino-runtime Show documentation
Rhino is an open-source implementation of JavaScript written entirely in Java.
It is typically embedded into Java applications to provide scripting to end users.
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.javascript;
import java.util.ArrayList;
import org.mozilla.javascript.TopLevel.NativeErrors;
public class NativePromise extends ScriptableObject {
enum State {
PENDING,
FULFILLED,
REJECTED
}
enum ReactionType {
FULFILL,
REJECT
}
private State state = State.PENDING;
private Object result = null;
private boolean handled = false;
private ArrayList fulfillReactions = new ArrayList<>();
private ArrayList rejectReactions = new ArrayList<>();
public static void init(Context cx, Scriptable scope, boolean sealed) {
LambdaConstructor constructor =
new LambdaConstructor(
scope,
"Promise",
1,
LambdaConstructor.CONSTRUCTOR_NEW,
NativePromise::constructor);
constructor.setStandardPropertyAttributes(DONTENUM | READONLY);
constructor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT);
constructor.defineConstructorMethod(
scope, "resolve", 1, NativePromise::resolve, DONTENUM, DONTENUM | READONLY);
constructor.defineConstructorMethod(
scope, "reject", 1, NativePromise::reject, DONTENUM, DONTENUM | READONLY);
constructor.defineConstructorMethod(
scope, "all", 1, NativePromise::all, DONTENUM, DONTENUM | READONLY);
constructor.defineConstructorMethod(
scope, "race", 1, NativePromise::race, DONTENUM, DONTENUM | READONLY);
ScriptableObject speciesDescriptor = (ScriptableObject) cx.newObject(scope);
ScriptableObject.putProperty(speciesDescriptor, "enumerable", false);
ScriptableObject.putProperty(speciesDescriptor, "configurable", true);
ScriptableObject.putProperty(
speciesDescriptor,
"get",
new LambdaFunction(
scope,
"get [Symbol.species]",
0,
(Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) ->
constructor));
constructor.defineOwnProperty(cx, SymbolKey.SPECIES, speciesDescriptor, false);
constructor.definePrototypeMethod(
scope,
"then",
2,
(Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> {
NativePromise self =
LambdaConstructor.convertThisObject(thisObj, NativePromise.class);
return self.then(lcx, lscope, constructor, args);
},
DONTENUM,
DONTENUM | READONLY);
constructor.definePrototypeMethod(
scope, "catch", 1, NativePromise::doCatch, DONTENUM, DONTENUM | READONLY);
constructor.definePrototypeMethod(
scope,
"finally",
1,
(Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) ->
doFinally(lcx, lscope, thisObj, constructor, args),
DONTENUM,
DONTENUM | READONLY);
constructor.definePrototypeProperty(
SymbolKey.TO_STRING_TAG, "Promise", DONTENUM | READONLY);
ScriptableObject.defineProperty(scope, "Promise", constructor, DONTENUM);
if (sealed) {
constructor.sealObject();
}
}
private static Scriptable constructor(Context cx, Scriptable scope, Object[] args) {
if (args.length < 1 || !(args[0] instanceof Callable)) {
throw ScriptRuntime.typeErrorById("msg.function.expected");
}
Callable executor = (Callable) args[0];
NativePromise promise = new NativePromise();
ResolvingFunctions resolving = new ResolvingFunctions(scope, promise);
Scriptable thisObj = Undefined.SCRIPTABLE_UNDEFINED;
if (!cx.isStrictMode()) {
Scriptable tcs = cx.topCallScope;
if (tcs != null) {
thisObj = tcs;
}
}
try {
executor.call(cx, scope, thisObj, new Object[] {resolving.resolve, resolving.reject});
} catch (RhinoException re) {
resolving.reject.call(cx, scope, thisObj, new Object[] {getErrorObject(cx, scope, re)});
}
return promise;
}
@Override
public String getClassName() {
return "Promise";
}
Object getResult() {
return result;
}
// Promise.resolve
private static Object resolve(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
if (!ScriptRuntime.isObject(thisObj)) {
throw ScriptRuntime.typeErrorById("msg.arg.not.object", ScriptRuntime.typeof(thisObj));
}
Object arg = (args.length > 0 ? args[0] : Undefined.instance);
return resolveInternal(cx, scope, thisObj, arg);
}
// PromiseResolve abstract operation
private static Object resolveInternal(
Context cx, Scriptable scope, Object constructor, Object arg) {
if (arg instanceof NativePromise) {
Object argConstructor = ScriptRuntime.getObjectProp(arg, "constructor", cx, scope);
if (argConstructor == constructor) {
return arg;
}
}
Capability cap = new Capability(cx, scope, constructor);
cap.resolve.call(cx, scope, Undefined.SCRIPTABLE_UNDEFINED, new Object[] {arg});
return cap.promise;
}
// Promise.reject
private static Object reject(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
if (!ScriptRuntime.isObject(thisObj)) {
throw ScriptRuntime.typeErrorById("msg.arg.not.object", ScriptRuntime.typeof(thisObj));
}
Object arg = (args.length > 0 ? args[0] : Undefined.instance);
Capability cap = new Capability(cx, scope, thisObj);
cap.reject.call(cx, scope, Undefined.SCRIPTABLE_UNDEFINED, new Object[] {arg});
return cap.promise;
}
// Promise.all
private static Object all(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
Capability cap = new Capability(cx, scope, thisObj);
Object arg = (args.length > 0 ? args[0] : Undefined.instance);
IteratorLikeIterable iterable;
try {
Object maybeIterable = ScriptRuntime.callIterator(arg, cx, scope);
iterable = new IteratorLikeIterable(cx, scope, maybeIterable);
} catch (RhinoException re) {
cap.reject.call(
cx,
scope,
Undefined.SCRIPTABLE_UNDEFINED,
new Object[] {getErrorObject(cx, scope, re)});
return cap.promise;
}
IteratorLikeIterable.Itr iterator = iterable.iterator();
try {
PromiseAllResolver resolver = new PromiseAllResolver(iterator, thisObj, cap);
try {
return resolver.resolve(cx, scope);
} finally {
if (!iterator.isDone()) {
iterable.close();
}
}
} catch (RhinoException re) {
cap.reject.call(
cx,
scope,
Undefined.SCRIPTABLE_UNDEFINED,
new Object[] {getErrorObject(cx, scope, re)});
return cap.promise;
}
}
// Promise.race
private static Object race(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
Capability cap = new Capability(cx, scope, thisObj);
Object arg = (args.length > 0 ? args[0] : Undefined.instance);
IteratorLikeIterable iterable;
try {
Object maybeIterable = ScriptRuntime.callIterator(arg, cx, scope);
iterable = new IteratorLikeIterable(cx, scope, maybeIterable);
} catch (RhinoException re) {
cap.reject.call(
cx,
scope,
Undefined.SCRIPTABLE_UNDEFINED,
new Object[] {getErrorObject(cx, scope, re)});
return cap.promise;
}
IteratorLikeIterable.Itr iterator = iterable.iterator();
try {
try {
return performRace(cx, scope, iterator, thisObj, cap);
} finally {
if (!iterator.isDone()) {
iterable.close();
}
}
} catch (RhinoException re) {
cap.reject.call(
cx,
scope,
Undefined.SCRIPTABLE_UNDEFINED,
new Object[] {getErrorObject(cx, scope, re)});
return cap.promise;
}
}
private static Object performRace(
Context cx,
Scriptable scope,
IteratorLikeIterable.Itr iterator,
Scriptable thisObj,
Capability cap) {
Callable resolve = ScriptRuntime.getPropFunctionAndThis(thisObj, "resolve", cx, scope);
Scriptable localThis = ScriptRuntime.lastStoredScriptable(cx);
// Manually iterate for exception handling purposes
while (true) {
boolean hasNext;
Object nextVal = Undefined.instance;
boolean nextOk = false;
try {
hasNext = iterator.hasNext();
if (hasNext) {
nextVal = iterator.next();
}
nextOk = true;
} finally {
if (!nextOk) {
iterator.setDone(true);
}
}
if (!hasNext) {
return cap.promise;
}
// Call "resolve" to get the next promise in the chain
Object nextPromise = resolve.call(cx, scope, localThis, new Object[] {nextVal});
// And then call "then" on it.
// Logic in the resolution function ensures we don't deliver duplicate results
Callable thenFunc =
ScriptRuntime.getPropFunctionAndThis(nextPromise, "then", cx, scope);
thenFunc.call(
cx,
scope,
ScriptRuntime.lastStoredScriptable(cx),
new Object[] {cap.resolve, cap.reject});
}
}
// Promise.prototype.then
private Object then(
Context cx, Scriptable scope, LambdaConstructor defaultConstructor, Object[] args) {
Constructable constructable =
AbstractEcmaObjectOperations.speciesConstructor(cx, this, defaultConstructor);
Capability capability = new Capability(cx, scope, constructable);
Callable onFulfilled = null;
if (args.length >= 1 && args[0] instanceof Callable) {
onFulfilled = (Callable) args[0];
}
Callable onRejected = null;
if (args.length >= 2 && args[1] instanceof Callable) {
onRejected = (Callable) args[1];
}
Reaction fulfillReaction = new Reaction(capability, ReactionType.FULFILL, onFulfilled);
Reaction rejectReaction = new Reaction(capability, ReactionType.REJECT, onRejected);
if (state == State.PENDING) {
fulfillReactions.add(fulfillReaction);
rejectReactions.add(rejectReaction);
} else if (state == State.FULFILLED) {
cx.enqueueMicrotask(() -> fulfillReaction.invoke(cx, scope, result));
} else {
assert (state == State.REJECTED);
if (!handled) {
cx.getUnhandledPromiseTracker().promiseHandled(this);
}
cx.enqueueMicrotask(() -> rejectReaction.invoke(cx, scope, result));
}
handled = true;
return capability.promise;
}
// Promise.prototype.catch
private static Object doCatch(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
Object arg = (args.length > 0 ? args[0] : Undefined.instance);
Scriptable coercedThis = ScriptRuntime.toObject(cx, scope, thisObj);
// No guarantee that the caller didn't change the prototype of "then"!
Callable thenFunc = ScriptRuntime.getPropFunctionAndThis(coercedThis, "then", cx, scope);
return thenFunc.call(
cx,
scope,
ScriptRuntime.lastStoredScriptable(cx),
new Object[] {Undefined.instance, arg});
}
// Promise.prototype.finally
private static Object doFinally(
Context cx,
Scriptable scope,
Scriptable thisObj,
LambdaConstructor defaultConstructor,
Object[] args) {
if (!ScriptRuntime.isObject(thisObj)) {
throw ScriptRuntime.typeErrorById("msg.arg.not.object", ScriptRuntime.typeof(thisObj));
}
Object onFinally = args.length > 0 ? args[0] : Undefined.SCRIPTABLE_UNDEFINED;
Object thenFinally = onFinally;
Object catchFinally = onFinally;
Constructable constructor =
AbstractEcmaObjectOperations.speciesConstructor(cx, thisObj, defaultConstructor);
if (onFinally instanceof Callable) {
Callable callableOnFinally = (Callable) thenFinally;
thenFinally = makeThenFinally(scope, constructor, callableOnFinally);
catchFinally = makeCatchFinally(scope, constructor, callableOnFinally);
}
Callable thenFunc = ScriptRuntime.getPropFunctionAndThis(thisObj, "then", cx, scope);
Scriptable to = ScriptRuntime.lastStoredScriptable(cx);
return thenFunc.call(cx, scope, to, new Object[] {thenFinally, catchFinally});
}
// Abstract "Then Finally Function"
private static Callable makeThenFinally(
Scriptable scope, Object constructor, Callable onFinally) {
return new LambdaFunction(
scope,
1,
(Context cx, Scriptable ls, Scriptable thisObj, Object[] args) -> {
Object value = args.length > 0 ? args[0] : Undefined.instance;
LambdaFunction valueThunk =
new LambdaFunction(
scope,
0,
(Context vc, Scriptable vs, Scriptable vt, Object[] va) ->
value);
Object result =
onFinally.call(
cx,
ls,
Undefined.SCRIPTABLE_UNDEFINED,
ScriptRuntime.emptyArgs);
Object promise = resolveInternal(cx, scope, constructor, result);
Callable thenFunc =
ScriptRuntime.getPropFunctionAndThis(promise, "then", cx, scope);
return thenFunc.call(
cx,
scope,
ScriptRuntime.lastStoredScriptable(cx),
new Object[] {valueThunk});
});
}
// Abstract "Catch Finally Thrower"
private static Callable makeCatchFinally(
Scriptable scope, Object constructor, Callable onFinally) {
return new LambdaFunction(
scope,
1,
(Context cx, Scriptable ls, Scriptable thisObj, Object[] args) -> {
Object reason = args.length > 0 ? args[0] : Undefined.instance;
LambdaFunction reasonThrower =
new LambdaFunction(
scope,
0,
(Context vc, Scriptable vs, Scriptable vt, Object[] va) -> {
throw new JavaScriptException(reason, null, 0);
});
Object result =
onFinally.call(
cx,
ls,
Undefined.SCRIPTABLE_UNDEFINED,
ScriptRuntime.emptyArgs);
Object promise = resolveInternal(cx, scope, constructor, result);
Callable thenFunc =
ScriptRuntime.getPropFunctionAndThis(promise, "then", cx, scope);
return thenFunc.call(
cx,
scope,
ScriptRuntime.lastStoredScriptable(cx),
new Object[] {reasonThrower});
});
}
// Abstract operation to fulfill a promise
private Object fulfillPromise(Context cx, Scriptable scope, Object value) {
assert (state == State.PENDING);
result = value;
ArrayList reactions = fulfillReactions;
fulfillReactions = new ArrayList<>();
if (!rejectReactions.isEmpty()) {
rejectReactions = new ArrayList<>();
}
state = State.FULFILLED;
for (Reaction r : reactions) {
cx.enqueueMicrotask(() -> r.invoke(cx, scope, value));
}
return Undefined.instance;
}
// Abstract operation to reject a promise.
private Object rejectPromise(Context cx, Scriptable scope, Object reason) {
assert (state == State.PENDING);
result = reason;
ArrayList reactions = rejectReactions;
rejectReactions = new ArrayList<>();
if (!fulfillReactions.isEmpty()) {
fulfillReactions = new ArrayList<>();
}
state = State.REJECTED;
cx.getUnhandledPromiseTracker().promiseRejected(this);
for (Reaction r : reactions) {
cx.enqueueMicrotask(() -> r.invoke(cx, scope, reason));
}
return Undefined.instance;
}
// Promise Resolve Thenable Job.
// This gets called by the "resolving func" as a microtask.
private void callThenable(Context cx, Scriptable scope, Object resolution, Callable thenFunc) {
ResolvingFunctions resolving = new ResolvingFunctions(scope, this);
Scriptable thisObj =
(resolution instanceof Scriptable
? (Scriptable) resolution
: Undefined.SCRIPTABLE_UNDEFINED);
try {
thenFunc.call(cx, scope, thisObj, new Object[] {resolving.resolve, resolving.reject});
} catch (RhinoException re) {
resolving.reject.call(
cx,
scope,
Undefined.SCRIPTABLE_UNDEFINED,
new Object[] {getErrorObject(cx, scope, re)});
}
}
private static Object getErrorObject(Context cx, Scriptable scope, RhinoException re) {
if (re instanceof JavaScriptException) {
return ((JavaScriptException) re).getValue();
}
TopLevel.NativeErrors constructor = NativeErrors.Error;
if (re instanceof EcmaError) {
EcmaError ee = (EcmaError) re;
switch (ee.getName()) {
case "EvalError":
constructor = NativeErrors.EvalError;
break;
case "RangeError":
constructor = NativeErrors.RangeError;
break;
case "ReferenceError":
constructor = NativeErrors.ReferenceError;
break;
case "SyntaxError":
constructor = NativeErrors.SyntaxError;
break;
case "TypeError":
constructor = NativeErrors.TypeError;
break;
case "URIError":
constructor = NativeErrors.URIError;
break;
case "InternalError":
constructor = NativeErrors.InternalError;
break;
case "JavaException":
constructor = NativeErrors.JavaException;
break;
default:
break;
}
}
return ScriptRuntime.newNativeError(cx, scope, constructor, new Object[] {re.getMessage()});
}
// Output of "CreateResolvingFunctions." Carries with it an "alreadyResolved" state,
// so we make it a separate object. This actually fires resolution functions on
// the passed callbacks.
private static class ResolvingFunctions {
private boolean alreadyResolved = false;
LambdaFunction resolve;
LambdaFunction reject;
ResolvingFunctions(Scriptable topScope, NativePromise promise) {
resolve =
new LambdaFunction(
topScope,
1,
(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) ->
resolve(
cx,
scope,
promise,
(args.length > 0 ? args[0] : Undefined.instance)));
resolve.setStandardPropertyAttributes(DONTENUM | READONLY);
reject =
new LambdaFunction(
topScope,
1,
(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) ->
reject(
cx,
scope,
promise,
(args.length > 0 ? args[0] : Undefined.instance)));
reject.setStandardPropertyAttributes(DONTENUM | READONLY);
}
private Object reject(Context cx, Scriptable scope, NativePromise promise, Object reason) {
if (alreadyResolved) {
return Undefined.instance;
}
alreadyResolved = true;
return promise.rejectPromise(cx, scope, reason);
}
private Object resolve(
Context cx, Scriptable scope, NativePromise promise, Object resolution) {
if (alreadyResolved) {
return Undefined.instance;
}
alreadyResolved = true;
if (resolution == promise) {
Object err =
ScriptRuntime.newNativeError(
cx,
scope,
NativeErrors.TypeError,
new Object[] {"No promise self-resolution"});
return promise.rejectPromise(cx, scope, err);
}
if (!ScriptRuntime.isObject(resolution)) {
return promise.fulfillPromise(cx, scope, resolution);
}
Scriptable sresolution = ScriptableObject.ensureScriptable(resolution);
Object thenObj = ScriptableObject.getProperty(sresolution, "then");
if (!(thenObj instanceof Callable)) {
return promise.fulfillPromise(cx, scope, resolution);
}
cx.enqueueMicrotask(
() -> promise.callThenable(cx, scope, resolution, (Callable) thenObj));
return Undefined.instance;
}
}
// "Promise Reaction" record. This is an input to the microtask.
private static class Reaction {
Capability capability;
ReactionType reaction = ReactionType.REJECT;
Callable handler;
Reaction(Capability cap, ReactionType type, Callable handler) {
this.capability = cap;
this.reaction = type;
this.handler = handler;
}
// Implementation of NewPromiseReactionJob
void invoke(Context cx, Scriptable scope, Object arg) {
try {
Object result = null;
if (handler == null) {
switch (reaction) {
case FULFILL:
result = arg;
break;
case REJECT:
capability.reject.call(
cx, scope, Undefined.SCRIPTABLE_UNDEFINED, new Object[] {arg});
return;
}
} else {
result =
handler.call(
cx, scope, Undefined.SCRIPTABLE_UNDEFINED, new Object[] {arg});
}
capability.resolve.call(
cx, scope, Undefined.SCRIPTABLE_UNDEFINED, new Object[] {result});
} catch (RhinoException re) {
capability.reject.call(
cx,
scope,
Undefined.SCRIPTABLE_UNDEFINED,
new Object[] {getErrorObject(cx, scope, re)});
}
}
}
// "Promise Capability Record"
// This abstracts a promise from the specific native implementation by keeping track
// of the "resolve" and "reject" functions.
private static class Capability {
Object promise;
private Object rawResolve = Undefined.instance;
Callable resolve;
private Object rawReject = Undefined.instance;
Callable reject;
// Given an object that represents a constructor function, execute it as if it
// meets the "Promise" constructor pattern, which takes a function that will
// be called with "resolve" and "reject" functions.
Capability(Context topCx, Scriptable topScope, Object pc) {
if (!(pc instanceof Constructable)) {
throw ScriptRuntime.typeErrorById("msg.constructor.expected");
}
Constructable promiseConstructor = (Constructable) pc;
LambdaFunction executorFunc =
new LambdaFunction(
topScope,
2,
(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) ->
executor(args));
executorFunc.setStandardPropertyAttributes(DONTENUM | READONLY);
promise = promiseConstructor.construct(topCx, topScope, new Object[] {executorFunc});
if (!(rawResolve instanceof Callable)) {
throw ScriptRuntime.typeErrorById("msg.function.expected");
}
resolve = (Callable) rawResolve;
if (!(rawReject instanceof Callable)) {
throw ScriptRuntime.typeErrorById("msg.function.expected");
}
reject = (Callable) rawReject;
}
private Object executor(Object[] args) {
if (!Undefined.isUndefined(rawResolve) || !Undefined.isUndefined(rawReject)) {
throw ScriptRuntime.typeErrorById("msg.promise.capability.state");
}
if (args.length > 0) {
rawResolve = args[0];
}
if (args.length > 1) {
rawReject = args[1];
}
return Undefined.instance;
}
}
// This object keeps track of the state necessary to execute Promise.all
private static class PromiseAllResolver {
// Limit the number of promises in Promise.all the same as it is in V8.
private static final int MAX_PROMISES = 1 << 21;
final ArrayList
© 2015 - 2025 Weber Informatics LLC | Privacy Policy