Index: base/android/java/src/org/chromium/base/Promise.java |
diff --git a/base/android/java/src/org/chromium/base/Promise.java b/base/android/java/src/org/chromium/base/Promise.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c3e493aa9ad226a83aa06daadd2bb0d1afd6ac61 |
--- /dev/null |
+++ b/base/android/java/src/org/chromium/base/Promise.java |
@@ -0,0 +1,318 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.base; |
+ |
+import android.os.Handler; |
+ |
+import java.util.LinkedList; |
+import java.util.List; |
+ |
+/** |
+ * A Promise class to be used as a placeholder for a result that will be provided asynchronously. |
+ * It must only be accessed from a single thread. |
+ * @param <T> The type the Promise will be fulfilled with. |
+ */ |
+public class Promise<T> { |
+ // TODO(peconn): Implement rejection handlers that can recover from rejection. |
+ |
+ // TODO(peconn): Add an IntDef here (https://crbug.com/623012). |
+ private static final int UNFULFILLED = 0; |
+ private static final int FULFILLED = 1; |
+ private static final int REJECTED = 2; |
+ |
+ private int mState = UNFULFILLED; |
+ |
+ private T mResult; |
+ private final List<Callback<T>> mFulfillCallbacks = new LinkedList<Callback<T>>(); |
+ |
+ private Exception mRejectReason; |
+ private final List<Callback<Exception>> mRejectCallbacks = |
+ new LinkedList<Callback<Exception>>(); |
+ |
+ private final Thread mThread; |
+ private final Handler mHandler; |
+ |
+ private boolean mThrowingRejectionHandler; |
+ |
+ /** |
+ * A function class for use when chaining Promises with {@link Promise#then(Function)}. |
+ */ |
+ public interface Function<A, R> { |
+ R apply(A argument); |
+ } |
+ |
+ /** |
+ * A function class for use when chaining Promises with {@link Promise#then(AsyncFunction)}. |
+ */ |
+ public interface AsyncFunction<A, R> { |
+ Promise<R> apply(A argument); |
+ } |
+ |
+ /** |
+ * An exception class for when a rejected Promise is not handled and cannot pass the rejection |
+ * to a subsequent Promise. |
+ */ |
+ public static class UnhandledRejectionException extends RuntimeException { |
+ public UnhandledRejectionException(String message, Throwable cause) { |
+ super(message, cause); |
+ } |
+ } |
+ |
+ /** |
+ * Creates an unfulfilled promise. |
+ */ |
+ public Promise() { |
+ mThread = Thread.currentThread(); |
+ mHandler = new Handler(); |
+ } |
+ |
+ /** |
+ * Convenience method that calls {@link #then(Callback, Callback)} providing a rejection |
+ * {@link Callback} that throws a {@link UnhandledRejectionException}. Only use this on |
+ * Promises that do not have rejection handlers or dependant Promises. |
+ */ |
+ public void then(Callback<T> onFulfill) { |
+ checkThread(); |
+ |
+ // Allow multiple single argument then(Callback)'s, but don't bother adding duplicate |
+ // throwing rejection handlers. |
+ if (mThrowingRejectionHandler) { |
+ thenInner(onFulfill); |
+ return; |
+ } |
+ |
+ assert mRejectCallbacks.size() == 0 : "Do not call the single argument " |
+ + "Promise.then(Callback) on a Promise that already has a rejection handler."; |
+ |
+ Callback<Exception> onReject = new Callback<Exception>() { |
+ @Override |
+ public void onResult(Exception reason) { |
+ throw new UnhandledRejectionException( |
+ "Promise was rejected without a rejection handler.", reason); |
+ } |
+ }; |
+ |
+ then(onFulfill, onReject); |
+ mThrowingRejectionHandler = true; |
+ } |
+ |
+ /** |
+ * Queues {@link Callback}s to be run when the Promise is either fulfilled or rejected. If the |
+ * Promise is already fulfilled or rejected, the appropriate callback will be run on the next |
+ * iteration of the message loop. |
+ * |
+ * @param onFulfill The Callback to be called on fulfillment. |
+ * @param onReject The Callback to be called on rejection. The argument to onReject will |
+ * may be null if the Promise was rejected manually. |
+ */ |
+ public void then(Callback<T> onFulfill, Callback<Exception> onReject) { |
+ checkThread(); |
+ |
+ thenInner(onFulfill); |
+ exceptInner(onReject); |
+ } |
+ |
+ /** |
+ * Adds a rejection handler to the Promise. This handler will be called if this Promise or any |
+ * Promises this Promise depends on is rejected or fails. The {@link Callback} will be given |
+ * the exception that caused the rejection, or null if the rejection was manual (caused by a |
+ * call to {@link #reject()}. |
+ */ |
+ public void except(Callback<Exception> onReject) { |
+ checkThread(); |
+ |
+ exceptInner(onReject); |
+ } |
+ |
+ private void thenInner(Callback<T> onFulfill) { |
+ if (mState == FULFILLED) { |
+ postCallbackToLooper(onFulfill, mResult); |
+ } else if (mState == UNFULFILLED) { |
+ mFulfillCallbacks.add(onFulfill); |
+ } |
+ } |
+ |
+ private void exceptInner(Callback<Exception> onReject) { |
+ assert !mThrowingRejectionHandler : "Do not add an exception handler to a Promise you have " |
+ + "called the single argument Promise.then(Callback) on."; |
+ |
+ if (mState == REJECTED) { |
+ postCallbackToLooper(onReject, mRejectReason); |
+ } else if (mState == UNFULFILLED) { |
+ mRejectCallbacks.add(onReject); |
+ } |
+ } |
+ |
+ /** |
+ * Queues a {@link Promise.Function} to be run when the Promise is fulfilled. When this Promise |
+ * is fulfilled, the function will be run and its result will be place in the returned Promise. |
+ */ |
+ public <R> Promise<R> then(final Function<T, R> function) { |
+ checkThread(); |
+ |
+ // Create a new Promise to store the result of the function. |
+ final Promise<R> promise = new Promise<R>(); |
+ |
+ // Once this Promise is fulfilled: |
+ // - Apply the given function to the result. |
+ // - Fulfill the new Promise. |
+ thenInner(new Callback<T>(){ |
+ @Override |
+ public void onResult(T result) { |
+ try { |
+ promise.fulfill(function.apply(result)); |
+ } catch (Exception e) { |
+ // If function application fails, reject the next Promise. |
+ promise.reject(e); |
+ } |
+ } |
+ }); |
+ |
+ // If this Promise is rejected, reject the next Promise. |
+ exceptInner(rejectPromiseCallback(promise)); |
+ |
+ return promise; |
+ } |
+ |
+ /** |
+ * Queues a {@link Promise.AsyncFunction} to be run when the Promise is fulfilled. When this |
+ * Promise is fulfilled, the AsyncFunction will be run. When the result of the AsyncFunction is |
+ * available, it will be placed in the returned Promise. |
+ */ |
+ public <R> Promise<R> then(final AsyncFunction<T, R> function) { |
+ checkThread(); |
+ |
+ // Create a new Promise to be returned. |
+ final Promise<R> promise = new Promise<R>(); |
+ |
+ // Once this Promise is fulfilled: |
+ // - Apply the given function to the result (giving us an inner Promise). |
+ // - On fulfillment of this inner Promise, fulfill our return Promise. |
+ thenInner(new Callback<T>() { |
+ @Override |
+ public void onResult(T result) { |
+ try { |
+ // When the inner Promise is fulfilled, fulfill the return Promise. |
+ // Alternatively, if the inner Promise is rejected, reject the return Promise. |
+ function.apply(result).then(new Callback<R>() { |
+ @Override |
+ public void onResult(R result) { |
+ promise.fulfill(result); |
+ } |
+ }, rejectPromiseCallback(promise)); |
+ } catch (Exception e) { |
+ // If creating the inner Promise failed, reject the next Promise. |
+ promise.reject(e); |
+ } |
+ |
+ } |
+ }); |
+ |
+ // If this Promise is rejected, reject the next Promise. |
+ exceptInner(rejectPromiseCallback(promise)); |
+ |
+ return promise; |
+ } |
+ |
+ /** |
+ * Fulfills the Promise with the result and passes it to any {@link Callback}s previously queued |
+ * on the next iteration of the message loop. |
+ */ |
+ public void fulfill(final T result) { |
+ checkThread(); |
+ assert mState == UNFULFILLED; |
+ |
+ mState = FULFILLED; |
+ mResult = result; |
+ |
+ for (final Callback<T> callback : mFulfillCallbacks) { |
+ postCallbackToLooper(callback, result); |
+ } |
+ |
+ mFulfillCallbacks.clear(); |
+ } |
+ |
+ /** |
+ * Rejects the Promise, rejecting all those Promises that rely on it. |
+ * |
+ * This may throw an exception if a dependent Promise fails to handle the rejection, so it is |
+ * important to make it explicit when a Promise may be rejected, so that users of that Promise |
+ * know to provide rejection handling. |
+ */ |
+ public void reject(final Exception reason) { |
+ checkThread(); |
+ assert mState == UNFULFILLED; |
+ |
+ mState = REJECTED; |
+ mRejectReason = reason; |
+ |
+ for (final Callback<Exception> callback : mRejectCallbacks) { |
+ postCallbackToLooper(callback, reason); |
+ } |
+ mRejectCallbacks.clear(); |
+ } |
+ |
+ /** |
+ * Rejects a Promise, see {@link #reject(Exception)}. |
+ */ |
+ public void reject() { |
+ reject(null); |
+ } |
+ |
+ /** |
+ * Returns whether the promise is fulfilled. |
+ */ |
+ public boolean isFulfilled() { |
+ checkThread(); |
+ |
+ return mState == FULFILLED; |
+ } |
+ |
+ /** |
+ * Returns whether the promise is rejected. |
+ */ |
+ public boolean isRejected() { |
+ checkThread(); |
+ |
+ return mState == REJECTED; |
+ } |
+ |
+ /** |
+ * Convenience method to return a Promise fulfilled with the given result. |
+ */ |
+ public static <T> Promise<T> fulfilled(T result) { |
+ Promise<T> promise = new Promise<T>(); |
+ promise.fulfill(result); |
+ return promise; |
+ } |
+ |
+ private void checkThread() { |
+ assert mThread == Thread.currentThread() : "Promise must only be used on a single Thread."; |
+ } |
+ |
+ // We use a different template parameter here so this can be used for both T and Throwables. |
+ private <S> void postCallbackToLooper(final Callback<S> callback, final S result) { |
+ // Post the callbacks to the Thread looper so we don't get a long chain of callbacks |
+ // holding up the thread. |
+ mHandler.post(new Runnable() { |
+ @Override |
+ public void run() { |
+ callback.onResult(result); |
+ } |
+ }); |
+ } |
+ |
+ /** |
+ * Convenience method to construct a callback that rejects the given Promise. |
+ */ |
+ private static <T> Callback<Exception> rejectPromiseCallback(final Promise<T> promise) { |
+ return new Callback<Exception>() { |
+ @Override |
+ public void onResult(Exception reason) { |
+ promise.reject(reason); |
+ } |
+ }; |
+ } |
+} |