Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(318)

Unified Diff: base/android/java/src/org/chromium/base/Promise.java

Issue 1885463002: Create a Promise class to simplify dealing with async results. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Remove IntDef Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « base/BUILD.gn ('k') | base/android/junit/src/org/chromium/base/PromiseTest.java » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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);
+ }
+ };
+ }
+}
« no previous file with comments | « base/BUILD.gn ('k') | base/android/junit/src/org/chromium/base/PromiseTest.java » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698