| // 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 android.support.annotation.IntDef; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| 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. |
| |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef({UNFULFILLED, FULFILLED, REJECTED}) |
| private @interface PromiseState {} |
| |
| private static final int UNFULFILLED = 0; |
| private static final int FULFILLED = 1; |
| private static final int REJECTED = 2; |
| |
| @PromiseState |
| private int mState = UNFULFILLED; |
| |
| private T mResult; |
| private final List<Callback<T>> mFulfillCallbacks = new LinkedList<>(); |
| |
| private Exception mRejectReason; |
| private final List<Callback<Exception>> mRejectCallbacks = new LinkedList<>(); |
| |
| private final Thread mThread = Thread.currentThread(); |
| private final Handler mHandler = new Handler(); |
| |
| private boolean mThrowingRejectionHandler; |
| |
| /** |
| * A function class for use when chaining Promises with {@link Promise#then(Function)}. |
| * @param <A> The type of the function input. |
| * @param <R> The type of the function output. |
| */ |
| public interface Function<A, R> { |
| R apply(A argument); |
| } |
| |
| /** |
| * A function class for use when chaining Promises with {@link Promise#then(AsyncFunction)}. |
| * @param <A> The type of the function input. |
| * @param <R> The type of the function output. |
| */ |
| 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); |
| } |
| } |
| |
| /** |
| * 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 = 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<>(); |
| |
| // Once this Promise is fulfilled: |
| // - Apply the given function to the result. |
| // - Fulfill the new Promise. |
| thenInner(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(promise::reject); |
| |
| 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<>(); |
| |
| // 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(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(promise::fulfill, promise::reject); |
| } 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(promise::reject); |
| |
| 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; |
| } |
| |
| /** |
| * Must be called after the promise has been fulfilled. |
| * |
| * @return The promised result. |
| */ |
| public T getResult() { |
| assert isFulfilled(); |
| return mResult; |
| } |
| |
| /** |
| * Convenience method to return a Promise fulfilled with the given result. |
| */ |
| public static <T> Promise<T> fulfilled(T result) { |
| Promise<T> promise = new Promise<>(); |
| 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(() -> callback.onResult(result)); |
| } |
| } |