diff --git a/jq/src/main/java/se/code77/jq/JQ.java b/jq/src/main/java/se/code77/jq/JQ.java index 1d2388b..1861234 100644 --- a/jq/src/main/java/se/code77/jq/JQ.java +++ b/jq/src/main/java/se/code77/jq/JQ.java @@ -139,6 +139,7 @@ public interface DeferredHandler { * operation is complete. * * @param Type of the promise you want to convert to an empty (value) value. + * @deprecated Use {@link Promise#thenResolve(Object)} instead */ public static final class OnFulfilledVoidCallback implements OnFulfilledCallback { @Override diff --git a/jq/src/main/java/se/code77/jq/Promise.java b/jq/src/main/java/se/code77/jq/Promise.java index e1f08ff..6a29613 100644 --- a/jq/src/main/java/se/code77/jq/Promise.java +++ b/jq/src/main/java/se/code77/jq/Promise.java @@ -165,6 +165,19 @@ public interface OnProgressedCallback { void onProgressed(float progress); } + /** + * Callback interface used for {@link #tap(OnTapCallback)} used to observe fulfilled promises + * without modifying the next promise. + * @param Type of value carried by the promise + */ + public interface OnTapCallback { + /** + * The promise has been fulfilled with the given value + * @param value Value + */ + void onTap(V value); + } + /** * An exception thrown by a promise which is terminated without any * rejection callback. A promise is terminated by calling @@ -375,6 +388,32 @@ public Promise then( */ public Promise then(OnFulfilledCallback onFulfilled); + /** + * Convenience method, equivalent to adding a fulfillment callback that merely returns the given value. + * + * @param nextValue Value to resolve the next promise with + * @param Type of the value carried by the next promise + * @return A new promise that, provided the current promise is resolved, will be resolved with the given value. + */ + public Promise thenResolve(NV nextValue); + + /** + * Convenience method, equivalent to adding a fulfillment callback that merely throws the given exception + * @param reason Exception to reject the next promise with + * @param Type of the value carried by the next promise + * @return A new promise that, provided the current promise is resolved, will be rejected with the given reason. + */ + public Promise thenReject(Exception reason, Class nextValueClass); + + /** + * Observe the state of this promise by adding a handler that will be invoked when the promise + * is fulfilled, but without modifying the next promise. + * + * @param onTap Simple, observe-only fulfillment handler + * @return A new promise which will inherit the state of the current promise + */ + public Promise tap(OnTapCallback onTap); + /** * Like {@link Promise#then(OnFulfilledCallback, OnRejectedCallback)} but only for promised List values. The * elements in the list will be spread as individual arguments on the supplied callback, which diff --git a/jq/src/main/java/se/code77/jq/PromiseImpl.java b/jq/src/main/java/se/code77/jq/PromiseImpl.java index 9f28809..74e9fd7 100644 --- a/jq/src/main/java/se/code77/jq/PromiseImpl.java +++ b/jq/src/main/java/se/code77/jq/PromiseImpl.java @@ -99,6 +99,41 @@ public Promise then(OnFulfilledCallback onFulfilled) { return then(onFulfilled, null); } + @Override + public Promise thenResolve(final NV nextValue) { + return then(new OnFulfilledCallback() { + @Override + public Future onFulfilled(V value) throws Exception { + return Value.wrap(nextValue); + } + }); + } + + @Override + public Promise thenReject(final Exception reason, Class nextValueClass) { + return then(new OnFulfilledCallback() { + @Override + public Future onFulfilled(V value) throws Exception { + throw reason; + } + }); + } + + @Override + public Promise tap(final OnTapCallback onTap) { + return then(new OnFulfilledCallback() { + @Override + public Future onFulfilled(V value) throws Exception { + try { + onTap.onTap(value); + } catch (RuntimeException e) { + // Swallow + } + return Value.wrap(value); + } + }); + } + @Override public Promise spread(final OnFulfilledSpreadCallback onFulfilled, OnRejectedCallback onRejected) { if (onFulfilled == null) { diff --git a/jq/src/test/java/se/code77/jq/PromiseTests.java b/jq/src/test/java/se/code77/jq/PromiseTests.java index 95acf02..2915f03 100644 --- a/jq/src/test/java/se/code77/jq/PromiseTests.java +++ b/jq/src/test/java/se/code77/jq/PromiseTests.java @@ -1022,4 +1022,73 @@ public Future onFulfilled(Double e1, Integer e2) throws Exception { assertData(spread, 500); } + @Test + public void thenResolve() { + final Promise p = JQ.resolve(1).thenResolve(TEST_VALUE1); + final BlockingDataHolder then1 = new BlockingDataHolder<>(); + + p.then(new DataFulfilledCallback(then1)); + + assertData(then1, 2000, TEST_VALUE1); + assertResolved(p, TEST_VALUE1); + } + + @Test + public void thenReject() { + final Promise p = JQ.resolve(1).thenReject(TEST_REASON1, String.class); + final BlockingDataHolder fail1 = new BlockingDataHolder<>(); + + p.fail(new DataRejectedCallback(fail1)); + + assertData(fail1, 2000, TEST_REASON1); + assertRejected(p, TEST_REASON1); + } + + @Test + public void tap_isResolved() { + final Promise p = JQ.resolve(TEST_VALUE1); + final BlockingDataHolder tap1 = new BlockingDataHolder<>(); + + p.tap(new Promise.OnTapCallback() { + @Override + public void onTap(String value) { + tap1.set(value); + } + }); + + assertData(tap1, 2000, TEST_VALUE1); + assertResolved(p, TEST_VALUE1); + } + + @Test + public void tap_isRejected() { + final Promise p = JQ.reject(TEST_REASON1); + final BlockingDataHolder tap1 = new BlockingDataHolder<>(); + + p.tap(new Promise.OnTapCallback() { + @Override + public void onTap(String value) { + tap1.set(value); + } + }); + + assertNoData(tap1, 2000); + assertRejected(p, TEST_REASON1); + } + + @Test + public void tap_isTapExceptionIgnored() { + final Promise p = JQ.resolve(TEST_VALUE1); + final BlockingDataHolder then1 = new BlockingDataHolder<>(); + + p.tap(new Promise.OnTapCallback() { + @Override + public void onTap(String value) { + throw new RuntimeException("Should be ignored"); + } + }).then(new DataFulfilledCallback<>(then1)); + + assertData(then1, 2000, TEST_VALUE1); + assertResolved(p, TEST_VALUE1); + } }