Created
June 17, 2017 08:22
-
-
Save loriopatrick/d5eb4df08095fa820cfc4e7a3cc88d4f to your computer and use it in GitHub Desktop.
Promises in Java modeled after NodeJS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.patricklorio.promise; | |
import java.util.ArrayList; | |
import java.util.function.Consumer; | |
/** | |
* @author plorio | |
*/ | |
public class Promise<T> { | |
public Promise(Async<T> async) { | |
try { | |
async.async(this::success, this::error); | |
} catch (Throwable throwable) { | |
error(throwable); | |
} | |
} | |
public static <K> Promise<K> resolve(K value) { | |
Promise<K> future = new Promise<>(); | |
future.success = value; | |
future.state = 1; | |
return future; | |
} | |
public static Promise<Void> reject(Throwable error) { | |
Promise<Void> future = new Promise<>(); | |
future.error = error; | |
future.state = 2; | |
return future; | |
} | |
private Promise() { | |
} | |
private final Object lock = new Object(); | |
private int state = 0; | |
private T success = null; | |
private Throwable error = null; | |
private final ArrayList<Consumer<T>> successListeners = new ArrayList<>(); | |
private final ArrayList<Consumer<Throwable>> errorListeners = new ArrayList<>(); | |
private void success(T value) { | |
synchronized (lock) { | |
if (state != 0) { | |
throw new IllegalStateException(); | |
} | |
state = 1; | |
success = value; | |
for (Consumer<T> listener : successListeners) { | |
listener.accept(value); | |
} | |
successListeners.clear(); | |
} | |
} | |
private void error(Throwable value) { | |
synchronized (lock) { | |
if (state != 0) { | |
throw new IllegalStateException(); | |
} | |
state = 2; | |
error = value; | |
for (Consumer<Throwable> listener : errorListeners) { | |
listener.accept(value); | |
} | |
errorListeners.clear(); | |
} | |
} | |
private void onSuccess(Consumer<T> listener) { | |
synchronized (lock) { | |
if (state == 1) { | |
listener.accept(success); | |
return; | |
} | |
if (state == 0) { | |
successListeners.add(listener); | |
} | |
} | |
} | |
private void onError(Consumer<Throwable> listener) { | |
synchronized (lock) { | |
if (state == 2) { | |
listener.accept(error); | |
return; | |
} | |
if (state == 0) { | |
errorListeners.add(listener); | |
} | |
} | |
} | |
public <R> Promise<R> then(ValueHandler<T, R> handler) { | |
Promise<R> future = new Promise<>(); | |
onSuccess(value -> { | |
final R handlerResult; | |
try { | |
handlerResult = handler.handle(value); | |
} catch (Throwable error) { | |
future.error(error); | |
return; | |
} | |
future.success(handlerResult); | |
}); | |
onError(future::error); | |
return future; | |
} | |
public Promise<Void> then(Handler<T> handler) { | |
Promise<Void> future = new Promise<>(); | |
onSuccess(value -> { | |
try { | |
handler.handle(value); | |
} catch (Throwable error) { | |
future.error(error); | |
return; | |
} | |
future.success(null); | |
}); | |
onError(future::error); | |
return future; | |
} | |
public <R> Promise<R> compose(ChainHandler<T, R> handler) { | |
Promise<R> future = new Promise<>(); | |
onSuccess(value -> { | |
final Promise<R> handlerResult; | |
try { | |
handlerResult = handler.handle(value); | |
} catch (Throwable error) { | |
future.error(error); | |
return; | |
} | |
handlerResult.onSuccess(future::success); | |
handlerResult.onError(future::error); | |
}); | |
onError(future::error); | |
return future; | |
} | |
public <E extends Throwable> Promise<T> exception(Class<E> errorClass, ValueHandler<E, T> handler) { | |
return exception(error -> { | |
if (errorClass.isAssignableFrom(errorClass)) { | |
return handler.handle(errorClass.cast(error)); | |
} | |
throw error; | |
}); | |
} | |
public Promise<T> exception(ValueHandler<Throwable, T> handler) { | |
Promise<T> future = new Promise<>(); | |
onError(error -> { | |
final T handlerResult; | |
try { | |
handlerResult = handler.handle(error); | |
} catch (Throwable error2) { | |
future.error(error2); | |
return; | |
} | |
future.success(handlerResult); | |
}); | |
onSuccess(future::success); | |
return future; | |
} | |
public <E extends Throwable> Promise<T> exceptionCompose(Class<E> errorClass, ChainHandler<E, T> handler) { | |
return exceptionCompose(error -> { | |
if (errorClass.isAssignableFrom(errorClass)) { | |
return handler.handle(errorClass.cast(error)); | |
} | |
throw error; | |
}); | |
} | |
public Promise<T> exceptionCompose(ChainHandler<Throwable, T> handler) { | |
Promise<T> future = new Promise<>(); | |
onError(error -> { | |
final Promise<T> handlerResult; | |
try { | |
handlerResult = handler.handle(error); | |
} catch (Throwable error2) { | |
future.error(error2); | |
return; | |
} | |
handlerResult.onSuccess(future::success); | |
handlerResult.onError(future::error); | |
}); | |
onSuccess(future::success); | |
return future; | |
} | |
public interface Async<T> { | |
void async(Consumer<T> resolve, Consumer<Throwable> reject) throws Throwable; | |
} | |
public interface Handler<T> { | |
void handle(T value) throws Throwable; | |
} | |
public interface ChainHandler<T, R> { | |
Promise<R> handle(T value) throws Throwable; | |
} | |
public interface ValueHandler<T, R> { | |
R handle(T value) throws Throwable; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment