JSO: add interface for Promise (#884)

This commit is contained in:
Bernd Busse 2024-02-17 19:11:04 +01:00 committed by GitHub
parent be09e698ea
commit 6788642ea9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 1254 additions and 0 deletions

View File

@ -0,0 +1,124 @@
/*
* Copyright 2023 Bernd Busse.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.jso.core;
import org.teavm.interop.NoSideEffects;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSMethod;
import org.teavm.jso.JSObject;
import org.teavm.jso.JSProperty;
import org.teavm.jso.util.function.JSConsumer;
import org.teavm.jso.util.function.JSFunction;
import org.teavm.jso.util.function.JSSupplier;
/**
* Interface for interacting with JavaScript
* <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">
* {@code Promise}s</a>.
*
* @param <T> The type this promise returns when resolving successfully.
*/
public abstract class JSPromise<T> implements JSObject {
private JSPromise() {
}
/** Interface for a function wrapped by a promise. */
@FunctionalInterface
@JSFunctor
public interface Executor<T> extends JSObject {
/**
* @param resolveFunc Call this function to resolve with success value.
* @param rejectFunc Call this function to reject with error value.
*/
void onExecute(JSConsumer<T> resolveFunc, JSConsumer<Object> rejectFunc);
}
@JSBody(params = "executor", script = "return new Promise(executor);")
@NoSideEffects
public static native <T> JSPromise<T> create(Executor<T> executor);
@JSBody(params = "promises", script = "return Promise.any(promises);")
@NoSideEffects
public static native <V> JSPromise<V> any(JSArrayReader<JSPromise<V>> promises);
// TODO: Allow passing differently typed promises via a JSTuple<T1, ...> interface
@JSBody(params = "promises", script = "return Promise.all(promises);")
@NoSideEffects
public static native <V> JSPromise<JSArrayReader<V>> all(JSArrayReader<JSPromise<V>> promises);
// TODO: Allow passing differently typed promises via a JSTuple<T1, ...> interface
@JSBody(params = "promises", script = "return Promise.allSettled(promises);")
@NoSideEffects
public static native <V> JSPromise<JSArrayReader<FulfillmentValue<V>>>
allSettled(JSArrayReader<JSPromise<V>> promises);
@JSBody(params = "promises", script = "return Promise.race(promises);")
@NoSideEffects
public static native <V> JSPromise<V> race(JSArrayReader<JSPromise<V>> promises);
@JSBody(params = "value", script = "return Promise.resolve(value);")
@NoSideEffects
public static native <V> JSPromise<V> resolve(V value);
@JSBody(params = "reason", script = "return Promise.reject(reason);")
@NoSideEffects
public static native <V> JSPromise<V> reject(Object reason);
/** Call {@code onFulfilled} with the success value, resolving with its return value. */
public abstract <V> JSPromise<V> then(JSFunction<T, V> onFulfilled);
/** Call {@code onFulfilled} with the success value or {@code onRejected} with the reject reason,
* resolving with its return value. */
public abstract <V> JSPromise<V> then(JSFunction<T, V> onFulfilled, JSFunction<Object, V> onRejected);
/** Call {@code onFulfilled} with the success value, returning a new promise. */
@JSMethod("then")
public abstract <V> JSPromise<V> flatThen(JSFunction<T, ? extends JSPromise<V>> onFulfilled);
/** Call {@code onFulfilled} with the success value or {@code onRejected} with the reject reason,
* returning a new promise. */
@JSMethod("then")
public abstract <V> JSPromise<V> flatThen(JSFunction<T, ? extends JSPromise<V>> onFulfilled,
JSFunction<Object, ? extends JSPromise<V>> onRejected);
/** Call {@code onRejected} with the reject reason, resolving with its return value. */
@JSMethod("catch")
public abstract <V> JSPromise<V> catchError(JSFunction<Object, V> onRejected);
/** Call {@code onRejected} with the reject reason, returning a new promise. */
@JSMethod("catch")
public abstract <V> JSPromise<V> flatCatchError(JSFunction<Object, ? extends JSPromise<V>> onRejected);
/** Call {@code onFinally} after settling, ignoring the return value. */
@JSMethod("finally")
public abstract JSPromise<T> onSettled(JSSupplier<Object> onFinally);
/** Interface for the return values of {@ref #allSettled()}. */
public interface FulfillmentValue<T> extends JSObject {
@JSProperty
@NoSideEffects
JSString getStatus();
@JSProperty
@NoSideEffects
T getValue();
@JSProperty
@NoSideEffects
Object getReason();
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2023 Bernd Busse.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.jso.util.function;
import java.util.Objects;
import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSObject;
@FunctionalInterface
@JSFunctor
public interface JSConsumer<T> extends JSObject {
void accept(T t);
default JSConsumer<T> andThen(JSConsumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> {
this.accept(t);
after.accept(t);
};
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2023 Bernd Busse.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.jso.util.function;
import java.util.Objects;
import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSObject;
@FunctionalInterface
@JSFunctor
public interface JSFunction<T, R> extends JSObject {
R apply(T t);
default <V> JSFunction<T, V> andThen(JSFunction<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> {
R result = this.apply(t);
return after.apply(result);
};
}
default <V> JSFunction<V, R> compose(JSFunction<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> {
T result = before.apply(v);
return this.apply(result);
};
}
static <T> JSFunction<T, T> identity() {
return (T t) -> {
return t;
};
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2023 Bernd Busse.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.jso.util.function;
import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSObject;
@FunctionalInterface
@JSFunctor
public interface JSSupplier<R> extends JSObject {
R get();
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2023 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
plugins {
java
war
id("org.teavm")
}
configurations {
create("war")
}
dependencies {
teavm(teavm.libs.jsoApis)
"war"(project(":stdout-helper", "war"))
}
teavm.js {
addedToWebApp = true
obfuscated = false
mainClass = "org.teavm.samples.promise.PromiseExample"
}
tasks.war {
dependsOn(configurations["war"])
from(provider { configurations["war"].map { zipTree(it) } })
}

View File

@ -0,0 +1,423 @@
/*
* Copyright 2024 Bernd Busse.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.samples.promise;
import java.util.Arrays;
import org.teavm.jso.JSBody;
import org.teavm.jso.browser.Window;
import org.teavm.jso.core.JSArray;
import org.teavm.jso.core.JSPromise;
import org.teavm.jso.core.JSString;
import org.teavm.jso.util.function.JSConsumer;
import org.teavm.jso.util.function.JSFunction;
import org.teavm.jso.util.function.JSSupplier;
public final class PromiseExample {
private static long start = System.currentTimeMillis();
private PromiseExample() {
}
public static void main(String[] args) throws InterruptedException {
report(Arrays.toString(args));
report("");
checkFunctionalInterface();
runSimplePromise();
runComplexPromise();
final var lock = new Object();
runLongRunningPromise(lock);
synchronized (lock) {
report("Lock acquired");
lock.wait(20000);
}
combinePromises(lock);
synchronized (lock) {
report("Lock acquired");
lock.wait(20000);
}
testNativePromises(lock);
report("Finished main thread");
}
private static void report(String message) {
var current = System.currentTimeMillis() - start;
System.out.println("[" + Thread.currentThread().getName() + "]/" + current + ": " + message);
}
private static void checkFunctionalInterface() {
JSSupplier<Integer> supplier = () -> 23;
JSFunction<Integer, Integer> addTwenty = value -> value + 20;
JSFunction<Integer, Integer> subTwenty = value -> value - 20;
JSFunction<Integer, Boolean> isPositive = value -> value >= 0;
JSConsumer<Integer> print = value -> report("My value: " + value.toString());
JSConsumer<Integer> print2 = value -> report("My value plus 10: " + Integer.valueOf(value + 10).toString());
var value = supplier.get();
report("Supplied value: " + value.toString());
value = addTwenty.apply(value);
report("Value plus 20: " + value.toString());
value = subTwenty.apply(value);
report("Value minus 20: " + value.toString());
var value2 = isPositive.apply(value);
report("Value is positive: " + value2.toString());
var subFourty = subTwenty.andThen(subTwenty);
value = subFourty.apply(value);
report("Value minus 40: " + value.toString());
var plusFourty = addTwenty.compose(addTwenty).andThen(addTwenty.compose(subTwenty));
value = plusFourty.apply(value);
report("Value plus 40: " + value.toString());
value2 = subFourty.andThen(isPositive).apply(value);
report("Value minus 40 is positive: " + value2.toString());
print.accept(value);
var printExtended = print.andThen(print2);
printExtended.accept(value);
}
private static void runSimplePromise() {
var promise = JSPromise.create((resolve, reject) -> {
report("Simple promise execution");
report("Resolving with 'success'");
resolve.accept("success");
});
}
private static void runComplexPromise() {
var promise = JSPromise.create((resolve, reject) -> {
report("Complex promise execution");
report("Resolving with 'step1'");
resolve.accept("step1");
})
.then(value -> {
report("Resolved with '" + value + "'");
report("... and resolve with 'step2'");
return "step2";
})
.then(value -> {
report("Resolved with '" + value + "'");
report("... and throw exception");
throw new RuntimeException("Exception in promise handler");
}, reason -> {
report("Failed unexpectedly with reason: " + reason.toString());
return reason.toString();
})
.then(value -> {
report("Resolved unexpectedly with '" + value + "'");
return value;
}, reason -> {
report("Failed expectedly with reason: " + reason.toString());
return reason.toString();
})
.flatThen(value -> {
report("Resolved with '" + value + "'");
report("... and resolve with resolved promise");
return JSPromise.resolve("step3");
})
.flatThen(value -> {
report("Resolved with '" + value + "'");
report("... and resolve with rejected promise");
return JSPromise.reject("step4");
})
.catchError(reason -> {
report("Catched reject reason '" + reason.toString() + "'");
return reason.toString();
})
.flatThen(value -> {
report("Resolved with '" + value + "'");
report("... and resolve with new promise");
return JSPromise.create((resolve, reject) -> {
report("Inner promise");
report("Reject with 'step from inner'");
reject.accept("step from inner");
});
})
.then(value -> {
report("Resolved unexpectedly with '" + value + "'");
return value;
})
.catchError(reason -> {
report("Catched reject reason '" + reason.toString() + "'");
return reason.toString();
})
.onSettled(() -> {
report("Promise has finally settled");
return null;
});
}
private static void runLongRunningPromise(Object lock) {
var promise = JSPromise.create((resolve, reject) -> {
report("Long promise exection");
report("Wait for a while...");
Window.setTimeout(() -> {
report("... and resolve with 'done'");
resolve.accept("done");
}, 2000);
}).then(value -> {
report("Resolved with '" + value + "'");
synchronized (lock) {
lock.notify();
}
return value;
});
}
private static void combinePromises(Object lock) throws InterruptedException {
JSArray<JSPromise<String>> promises = JSArray.create(3);
report("Start 3 successful promises");
promises.set(0, JSPromise.resolve("success1"));
promises.set(1, JSPromise.resolve("success2"));
promises.set(2, JSPromise.resolve("success3"));
var allPromises = JSPromise.all(promises);
allPromises.then(value -> {
report("All promises resolved to: " + value);
return "success";
}, reason -> {
report("At least one promise rejected with: " + reason.toString());
return "failure";
}).onSettled(() -> {
synchronized (lock) {
lock.notify();
}
return null;
});
synchronized (lock) {
report("Lock acquired");
lock.wait(20000);
}
report("Start 1 successful and 2 rejected promise");
promises.set(0, JSPromise.resolve("success1"));
promises.set(1, JSPromise.reject("failure2"));
promises.set(2, JSPromise.reject("failure3"));
allPromises = JSPromise.all(promises);
allPromises.then(value -> {
report("All promises resolved to: " + value);
return "success";
}, reason -> {
report("At least one promise rejected with: " + reason.toString());
return "failure";
}).onSettled(() -> {
synchronized (lock) {
lock.notify();
}
return null;
});
synchronized (lock) {
report("Lock acquired");
lock.wait(20000);
}
var settledPromises = JSPromise.allSettled(promises);
settledPromises.then(value -> {
report(Integer.toString(value.getLength()) + " promises settled to:");
for (int i = 0; i < value.getLength(); ++i) {
var item = value.get(i);
var msg = "-- Promise " + i + " " + item.getStatus() + " with: ";
if (item.getStatus().stringValue().equals("fulfilled")) {
msg += item.getValue().toString();
} else if (item.getStatus().stringValue().equals("rejected")) {
msg += item.getReason().toString();
}
report(msg);
}
return "success";
}).onSettled(() -> {
synchronized (lock) {
lock.notify();
}
return null;
});
synchronized (lock) {
report("Lock acquired");
lock.wait(20000);
}
var anyPromise = JSPromise.any(promises);
anyPromise.then(value -> {
report("At least one promise resolved to: " + value);
return "success";
}, reason -> {
report("All promises rejected with: " + reason.toString());
return "failure";
}).onSettled(() -> {
synchronized (lock) {
lock.notify();
}
return null;
});
synchronized (lock) {
report("Lock acquired");
lock.wait(20000);
}
report("Start 3 rejected promises");
promises.set(0, JSPromise.reject("failure1"));
promises.set(1, JSPromise.reject("failure2"));
promises.set(2, JSPromise.reject("failure3"));
anyPromise = JSPromise.any(promises);
anyPromise.then(value -> {
report("At least one promise resolved to: " + value);
return "success";
}, reason -> {
report("All promises rejected with: " + reason.toString());
return "failure";
}).onSettled(() -> {
synchronized (lock) {
lock.notify();
}
return null;
});
synchronized (lock) {
report("Lock acquired");
lock.wait(20000);
}
report("Start 3 delayed promises");
promises.set(0, JSPromise.create((resolve, reject) -> Window.setTimeout(() -> resolve.accept("success1"), 200)));
promises.set(1, JSPromise.create((resolve, reject) -> Window.setTimeout(() -> reject.accept("failure1"), 100)));
promises.set(2, JSPromise.create((resolve, reject) -> Window.setTimeout(() -> resolve.accept("success3"), 50)));
anyPromise = JSPromise.race(promises);
anyPromise.then(value -> {
report("First settled promise resolved to: " + value);
return "success";
}, reason -> {
report("First settled promise rejected with: " + reason.toString());
return "failure";
}).onSettled(() -> {
synchronized (lock) {
lock.notify();
}
return null;
});
synchronized (lock) {
report("Lock acquired");
lock.wait(20000);
}
report("Start 3 delayed promises");
promises.set(0, JSPromise.create((resolve, reject) -> Window.setTimeout(() -> resolve.accept("success1"), 200)));
promises.set(1, JSPromise.create((resolve, reject) -> Window.setTimeout(() -> reject.accept("failure1"), 50)));
promises.set(2, JSPromise.create((resolve, reject) -> Window.setTimeout(() -> resolve.accept("success3"), 100)));
anyPromise = JSPromise.race(promises);
anyPromise.then(value -> {
report("First settled promise resolved to: " + value);
return "success";
}, reason -> {
report("First settled promise rejected with: " + reason.toString());
return "failure";
}).onSettled(() -> {
synchronized (lock) {
lock.notify();
}
return null;
});
}
private static void testNativePromises(Object lock) throws InterruptedException {
report("Get promise from native method");
var nativePromise = getNativePromise(JSString.valueOf("success from native"));
nativePromise.then(value -> {
report("Native resolved expectedly with '" + value + "'");
return value;
}, reason -> {
report("Native rejected unexpectedly with '" + reason.toString() + "'");
return reason.toString();
}).onSettled(() -> {
synchronized (lock) {
lock.notify();
}
return null;
});
synchronized (lock) {
report("Lock acquired");
lock.wait(20000);
}
nativePromise = getNativeRejectingPromise(JSString.valueOf("failure from native"));
nativePromise.then(value -> {
report("Native resolved unexpectedly with '" + value + "'");
return value;
}, reason -> {
report("Native rejected expectedly with '" + reason.toString() + "'");
return reason.toString();
}).onSettled(() -> {
synchronized (lock) {
lock.notify();
}
return null;
});
synchronized (lock) {
report("Lock acquired");
lock.wait(20000);
}
report("Pass promise to native method");
handlePromise(JSPromise.create((resolve, reject) -> {
resolve.accept(JSString.valueOf("Resolved from Java"));
}));
handlePromise(JSPromise.create((resolve, reject) -> {
reject.accept(JSString.valueOf("Rejected from Java"));
}));
}
@JSBody(params = "msg", script = "return new Promise((resolve, reject) => {"
+ " setTimeout(() => resolve(msg), 500);"
+ "});")
private static native JSPromise<JSString> getNativePromise(JSString msg);
@JSBody(params = "msg", script = "return new Promise((resolve, reject) => {"
+ " setTimeout(() => reject(msg), 500);"
+ "});")
private static native JSPromise<JSString> getNativeRejectingPromise(JSString msg);
@JSBody(params = "promise", script = "promise.then("
+ " (value) => console.log('success:', value),"
+ " (reason) => console.log('failure:', reason));")
private static native void handlePromise(JSPromise<JSString> promise);
}

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2015 Alexey Andreev.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
</web-app>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,459 @@
<!--
Copyright 2015 Alexey Andreev.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!DOCTYPE html>
<html>
<head>
<title>Continuation-passing style demo</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<script type="text/javascript" charset="utf-8" src="teavm/stdout.js"></script>
<script type="text/javascript" charset="utf-8" src="js/promise.js"></script>
<script type="text/javascript" charset="utf-8" src="highlight.pack.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
<link rel="stylesheet" type="text/css" href="syntax.css">
<script type="text/javascript">
function runAll() {
hljs.highlightBlock(document.getElementById("source-code"));
main(["foo", "bar"]);
}
</script>
</head>
<body onload="runAll()">
<div id="description">This application shows how TeaVM can interact with JavaScript
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise"><code>Promise</code>s</a>
(see <a href="https://github.com/konsoletyper/teavm/tree/master/samples/promise">source code on GitHub</a>).</div>
<div id="blocks">
<div class="block" id="stdout-wrapper">
<div class="block-title">stdout</div>
<div class="block-content" id="stdout"></div>
</div>
<div class="block" id="source-wrapper">
<div class="block-title">Source code</div>
<pre class="block-content" id="source-code">
import java.util.Arrays;
import org.teavm.jso.JSBody;
import org.teavm.jso.browser.Window;
import org.teavm.jso.core.JSArray;
import org.teavm.jso.core.JSPromise;
import org.teavm.jso.core.JSString;
import org.teavm.jso.util.function.JSConsumer;
import org.teavm.jso.util.function.JSFunction;
import org.teavm.jso.util.function.JSSupplier;
public final class PromiseExample {
private static long start = System.currentTimeMillis();
private PromiseExample() {
}
public static void main(String[] args) throws InterruptedException {
report(Arrays.toString(args));
report("");
checkFunctionalInterface();
runSimplePromise();
runComplexPromise();
final var lock = new Object();
runLongRunningPromise(lock);
synchronized (lock) {
report("Lock acquired");
lock.wait(20000);
}
combinePromises(lock);
synchronized (lock) {
report("Lock acquired");
lock.wait(20000);
}
testNativePromises(lock);
report("Finished main thread");
}
private static void report(String message) {
var current = System.currentTimeMillis() - start;
System.out.println("[" + Thread.currentThread().getName() + "]/" + current + ": " + message);
}
private static void checkFunctionalInterface() {
JSSupplier<Integer> supplier = () -> 23;
JSFunction<Integer, Integer> addTwenty = value -> value + 20;
JSFunction<Integer, Integer> subTwenty = value -> value - 20;
JSFunction<Integer, Boolean> isPositive = value -> value >= 0;
JSConsumer<Integer> print = value -> report("My value: " + value.toString());
JSConsumer<Integer> print2 = value -> report("My value plus 10: " + Integer.valueOf(value + 10).toString());
var value = supplier.get();
report("Supplied value: " + value.toString());
value = addTwenty.apply(value);
report("Value plus 20: " + value.toString());
value = subTwenty.apply(value);
report("Value minus 20: " + value.toString());
var value2 = isPositive.apply(value);
report("Value is positive: " + value2.toString());
var subFourty = subTwenty.andThen(subTwenty);
value = subFourty.apply(value);
report("Value minus 40: " + value.toString());
var plusFourty = addTwenty.compose(addTwenty).andThen(addTwenty.compose(subTwenty));
value = plusFourty.apply(value);
report("Value plus 40: " + value.toString());
value2 = subFourty.andThen(isPositive).apply(value);
report("Value minus 40 is positive: " + value2.toString());
print.accept(value);
var printExtended = print.andThen(print2);
printExtended.accept(value);
}
private static void runSimplePromise() {
var promise = JSPromise.create((resolve, reject) -> {
report("Simple promise execution");
report("Resolving with 'success'");
resolve.accept("success");
});
}
private static void runComplexPromise() {
var promise = JSPromise.create((resolve, reject) -> {
report("Complex promise execution");
report("Resolving with 'step1'");
resolve.accept("step1");
})
.then(value -> {
report("Resolved with '" + value + "'");
report("... and resolve with 'step2'");
return "step2";
})
.then(value -> {
report("Resolved with '" + value + "'");
report("... and throw exception");
throw new RuntimeException("Exception in promise handler");
}, reason -> {
report("Failed unexpectedly with reason: " + reason.toString());
return reason.toString();
})
.then(value -> {
report("Resolved unexpectedly with '" + value + "'");
return value;
}, reason -> {
report("Failed expectedly with reason: " + reason.toString());
return reason.toString();
})
.flatThen(value -> {
report("Resolved with '" + value + "'");
report("... and resolve with resolved promise");
return JSPromise.resolve("step3");
})
.flatThen(value -> {
report("Resolved with '" + value + "'");
report("... and resolve with rejected promise");
return JSPromise.reject("step4");
})
.catchError(reason -> {
report("Catched reject reason '" + reason.toString() + "'");
return reason.toString();
})
.flatThen(value -> {
report("Resolved with '" + value + "'");
report("... and resolve with new promise");
return JSPromise.create((resolve, reject) -> {
report("Inner promise");
report("Reject with 'step from inner'");
reject.accept("step from inner");
});
})
.then(value -> {
report("Resolved unexpectedly with '" + value + "'");
return value;
})
.catchError(reason -> {
report("Catched reject reason '" + reason.toString() + "'");
return reason.toString();
})
.onSettled(() -> {
report("Promise has finally settled");
return null;
});
}
private static void runLongRunningPromise(Object lock) {
var promise = JSPromise.create((resolve, reject) -> {
report("Long promise exection");
report("Wait for a while...");
Window.setTimeout(() -> {
report("... and resolve with 'done'");
resolve.accept("done");
}, 2000);
}).then(value -> {
report("Resolved with '" + value + "'");
synchronized (lock) {
lock.notify();
}
return value;
});
}
private static void combinePromises(Object lock) throws InterruptedException {
JSArray<JSPromise<String>> promises = JSArray.create(3);
report("Start 3 successful promises");
promises.set(0, JSPromise.resolve("success1"));
promises.set(1, JSPromise.resolve("success2"));
promises.set(2, JSPromise.resolve("success3"));
var allPromises = JSPromise.all(promises);
allPromises.then(value -> {
report("All promises resolved to: " + value);
return "success";
}, reason -> {
report("At least one promise rejected with: " + reason.toString());
return "failure";
}).onSettled(() -> {
synchronized (lock) {
lock.notify();
}
return null;
});
synchronized (lock) {
report("Lock acquired");
lock.wait(20000);
}
report("Start 1 successful and 2 rejected promise");
promises.set(0, JSPromise.resolve("success1"));
promises.set(1, JSPromise.reject("failure2"));
promises.set(2, JSPromise.reject("failure3"));
allPromises = JSPromise.all(promises);
allPromises.then(value -> {
report("All promises resolved to: " + value);
return "success";
}, reason -> {
report("At least one promise rejected with: " + reason.toString());
return "failure";
}).onSettled(() -> {
synchronized (lock) {
lock.notify();
}
return null;
});
synchronized (lock) {
report("Lock acquired");
lock.wait(20000);
}
var settledPromises = JSPromise.allSettled(promises);
settledPromises.then(value -> {
report(Integer.toString(value.getLength()) + " promises settled to:");
for (int i = 0; i < value.getLength(); ++i) {
var item = value.get(i);
var msg = "-- Promise " + i + " " + item.getStatus() + " with: ";
if (item.getStatus().stringValue().equals("fulfilled")) {
msg += item.getValue().toString();
} else if (item.getStatus().stringValue().equals("rejected")) {
msg += item.getReason().toString();
}
report(msg);
}
return "success";
}).onSettled(() -> {
synchronized (lock) {
lock.notify();
}
return null;
});
synchronized (lock) {
report("Lock acquired");
lock.wait(20000);
}
var anyPromise = JSPromise.any(promises);
anyPromise.then(value -> {
report("At least one promise resolved to: " + value);
return "success";
}, reason -> {
report("All promises rejected with: " + reason.toString());
return "failure";
}).onSettled(() -> {
synchronized (lock) {
lock.notify();
}
return null;
});
synchronized (lock) {
report("Lock acquired");
lock.wait(20000);
}
report("Start 3 rejected promises");
promises.set(0, JSPromise.reject("failure1"));
promises.set(1, JSPromise.reject("failure2"));
promises.set(2, JSPromise.reject("failure3"));
anyPromise = JSPromise.any(promises);
anyPromise.then(value -> {
report("At least one promise resolved to: " + value);
return "success";
}, reason -> {
report("All promises rejected with: " + reason.toString());
return "failure";
}).onSettled(() -> {
synchronized (lock) {
lock.notify();
}
return null;
});
synchronized (lock) {
report("Lock acquired");
lock.wait(20000);
}
report("Start 3 delayed promises");
promises.set(0, JSPromise.create((resolve, reject) -> Window.setTimeout(() -> resolve.accept("success1"), 200)));
promises.set(1, JSPromise.create((resolve, reject) -> Window.setTimeout(() -> reject.accept("failure1"), 100)));
promises.set(2, JSPromise.create((resolve, reject) -> Window.setTimeout(() -> resolve.accept("success3"), 50)));
anyPromise = JSPromise.race(promises);
anyPromise.then(value -> {
report("First settled promise resolved to: " + value);
return "success";
}, reason -> {
report("First settled promise rejected with: " + reason.toString());
return "failure";
}).onSettled(() -> {
synchronized (lock) {
lock.notify();
}
return null;
});
synchronized (lock) {
report("Lock acquired");
lock.wait(20000);
}
report("Start 3 delayed promises");
promises.set(0, JSPromise.create((resolve, reject) -> Window.setTimeout(() -> resolve.accept("success1"), 200)));
promises.set(1, JSPromise.create((resolve, reject) -> Window.setTimeout(() -> reject.accept("failure1"), 50)));
promises.set(2, JSPromise.create((resolve, reject) -> Window.setTimeout(() -> resolve.accept("success3"), 100)));
anyPromise = JSPromise.race(promises);
anyPromise.then(value -> {
report("First settled promise resolved to: " + value);
return "success";
}, reason -> {
report("First settled promise rejected with: " + reason.toString());
return "failure";
}).onSettled(() -> {
synchronized (lock) {
lock.notify();
}
return null;
});
}
private static void testNativePromises(Object lock) throws InterruptedException {
report("Get promise from native method");
var nativePromise = getNativePromise(JSString.valueOf("success from native"));
nativePromise.then(value -> {
report("Native resolved expectedly with '" + value + "'");
return value;
}, reason -> {
report("Native rejected unexpectedly with '" + reason.toString() + "'");
return reason.toString();
}).onSettled(() -> {
synchronized (lock) {
lock.notify();
}
return null;
});
synchronized (lock) {
report("Lock acquired");
lock.wait(20000);
}
nativePromise = getNativeRejectingPromise(JSString.valueOf("failure from native"));
nativePromise.then(value -> {
report("Native resolved unexpectedly with '" + value + "'");
return value;
}, reason -> {
report("Native rejected expectedly with '" + reason.toString() + "'");
return reason.toString();
}).onSettled(() -> {
synchronized (lock) {
lock.notify();
}
return null;
});
synchronized (lock) {
report("Lock acquired");
lock.wait(20000);
}
report("Pass promise to native method");
handlePromise(JSPromise.create((resolve, reject) -> {
resolve.accept(JSString.valueOf("Resolved from Java"));
}));
handlePromise(JSPromise.create((resolve, reject) -> {
reject.accept(JSString.valueOf("Rejected from Java"));
}));
}
@JSBody(params = "msg", script = "return new Promise((resolve, reject) => {"
+ " setTimeout(() => resolve(msg), 500);"
+ "});")
private static native JSPromise<JSString> getNativePromise(JSString msg);
@JSBody(params = "msg", script = "return new Promise((resolve, reject) => {"
+ " setTimeout(() => reject(msg), 500);"
+ "});")
private static native JSPromise<JSString> getNativeRejectingPromise(JSString msg);
@JSBody(params = "promise", script = "promise.then("
+ " (value) => console.log('success:', value),"
+ " (reason) => console.log('failure:', reason));")
private static native void handlePromise(JSPromise<JSString> promise);
}
</pre>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,62 @@
html, body {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
margin: 0;
padding: 0;
}
.block {
position: absolute;
left: 0;
right: 0;
}
.block-title {
position: absolute;
top: 10px;
height: 20px;
left: 10px;
right: 10px;
font-weight: bold;
}
.block-content {
position: absolute;
top: 30px;
bottom: 5px;
left: 10px;
right: 10px;
background-color: rgb(240,240,240);
border-width: 1px;
border-style: solid;
border-color: rgb(210,210,210);
overflow: auto;
padding: 3px;
margin: 0;
border-radius: 3px;
}
#description {
position: absolute;
top: 10px;
height: 40px;
left: 10px;
right: 10px;
}
#blocks {
position: absolute;
top: 50px;
bottom: 0;
left: 0;
right: 0;
}
#stdout-wrapper {
top: 0;
height: 50%;
}
#source-wrapper {
top: 50%;
bottom: 0;
}

View File

@ -0,0 +1,12 @@
.hljs-keyword {
font-weight: bold;
}
.hljs-class .hljs-title {
color: #9BB01D;
}
.hljs-annotation {
color: #FFA500;
}
.hljs-string, .hljs-number {
color: red;
}

View File

@ -56,6 +56,7 @@ include("hello")
include("async")
include("benchmark")
include("pi")
include("promise")
include("kotlin")
include("scala")
include("web-apis")