A simple promise library for Android.
The library can work with java and kotlin.
The purpose and use of this library is the following.
- Avoiding the callback hell on asynchronous operations.
- Propagating the exception, which occurred in the callback, to the next callback.
- Canceling the asynchronous operation immediately, even if you had used blocking API, such as the
java.lang.Thread#sleep()
. And detecting canceled.
loilo-promise is available on Marven Central.
via Gradle
dependencies {
compile 'tv.loilo.promise:promise:0.8.2'
compile 'tv.loilo.promise:promise-support:0.8.2'
}
Import the following files to your project.
Kotlin seems to require the build by same version of kotlin compiler in all module(?). Thus building our Kotlin code in your project looks like better than that your project depends to modules built by us.
Promise can chain asynchronous callbacks.
by Java
final Canceller canceller = Promises.when(new WhenCallback<String>() {
@Override
public Deferred<String> run(WhenParams params) throws Exception {
return doSomething(params);
}
}).succeeded(new SuccessCallback<String, String>() {
@Override
public Deferred<String> run(SuccessParams<String> params) throws Exception {
return handleSuccessResult(params);
}
}).failed(new FailCallback<String>() {
@Override
public Deferred<String> run(FailParams<String> params) throws Exception {
return handleFailResult(params);
}
}).then(new ThenCallback<String, String>() {
@Override
public Deferred<String> run(ThenParams<String> params) throws Exception {
return handleSuccessOrFailOrCancelResult(params);
}
}).watch(new WatchCallback<String>() {
@Override
public void run(final WatchParams<String> params) throws Exception {
watchSuccessOrFailOrCancelResult(params);
}
}).finish(new FinishCallback<String>() {
@Override
public void run(final FinishParams<String> params) {
// Dispatch main thread.
Dispatcher.getMainDispatcher().run(new Runnable() {
@Override
public void run() {
if (params.getCancelToken().isCanceled()) {
//Promise is canceled.
return;
}
final Exception exception = params.getException();
if (e == null) {
//Promise is failed
return;
}
//Promise is succeeded.
final String value = params.getValue();
}
});
}
}).submit();
by Kotlin
val canceller = promiseWhen {
doSomething(it)
}.succeeded {
handleSuccessResult(it)
}.failed {
handleFailResult(it)
}.then {
handleSuccessOrFailOrCancelResult(it)
}.watch {
watchSuccessOrFailOrCancelResult(it)
}.finish {
//Dispatch main thread.
runOnUi {
it.whenSucceeded({
//Promise succeeded.
val value = it
}, whenFailed = {
//Promise failed.
val exception = it
}) ?: run{
//Promise canceled.
}
}
}.submit()
When you call submit()
, the library executes callback chain asynchronously.
Result of Promise takes one of the following three states.
- Succeeded.
- Failed.
- Canceled.
You will receive such a result at callbacks.
When unhandled exception occurred on your callback implementation, you can get the exception at chained callbacks.
The library support cancellation of the asynchronous calls.
Cancellation will be notified by the boolean flag and the
java.lang.Thread.interrupt()
to your callbacks.
By java.lang.Thread.interrupt()
, you can cancel blocking API immediately,
such as java.lang.Thread.sleep()
and java.util.concurrent.locks.Lock.lockInterruptibly()
.
You can cancel promise by calling Canceller.cancel()
.
by Java
final Canceller canceller = Promises.when(new WhenCallback<String>() {
@Override
public Deferred<String> run(WhenParams params) throws Exception {
return doSomething(params);
}
}).submit();
canceller.cancel()
by Kotlin
val canceller = promiseWhen {
doSomething(it)
}.submit()
canceller.cancel()
In addition,
when java.lang.InterruptedException
or java.util.concurrent.CancellationException
thrown,
the library catches them and interprets as that promise was canceled.
You can call promise in promise callbacks.
by Java
Promises.when(new WhenCallback<String>() {
@Override
public Deferred<String> run(WhenParams params) throws Exception {
return Promises.when(new WhenCallback<String>() {
@Override
public Deferred<String> run(WhenParams params) throws Exception {
return doSomething(params);
}
}).get(params);
}
}).submit();
by Kotlin
promiseWhen {
promiseWhen {
doSomething(it)
}.get(it)
}.submit()
You can use other asynchronous API in promise callbacks by Deferrable
class.
by Java
Promises.when(new WhenCallback<String>() {
@Override
public Deferred<String> run(WhenParams params) throws Exception {
final Deferrable<String> deferrable = new Deferrable<>();
final Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
if(params.getCancelToken().isCanceled()){
//Notify canceled.
deferrable.setCanceled();
return;
}
//Do your asynchronous process.
Thread.sleep(5000);
//Notify succeeded and value.
deferrable.setSucceeded("Sleep succeeded.");
} catch (InterruptedException e) {
//Notify canceled.
deferrable.setCanceled();
} catch (Exception e){
//Notify failed and cause.
deferrable.setFailed(e);
}
}
});
deferrable.setCancellable(new Cancellable() {
@Override
public void cancel() {
//Cancel thread.
thread.interrupt();
}
});
thread.start();
return deferrable;
}
}).finish(new FinishCallback<String>() {
@Override
public void run(FinishParams<String> params) {
//Calling on main thread.
if (params.getCancelToken().isCanceled()) {
//Promise is canceled.
return;
}
final Exception e = params.getException();
if (e == null) {
//Promise is failed
return;
}
//Promise is succeeded.
final String value = params.getValue();
assert(value == "Sleep succeeded.");
}
}).submit();
by Kotlin
promiseWhen { p ->
deferrable<String> { d ->
val thread = Thread {
try{
if(p.cancelToken.isCanceled){
//Notify canceled.
d.setCanceled()
} else {
//Do your asynchronous process.
Thread.sleep(5000)
//Notify succeeded and value.
d.setSucceeded("Sleep succeeded.")
}
} catch (e : InterruptedException){
//Notify canceled.
d.setCanceled()
} catch (e : Exception){
//Notify failed and cause.
d.setFailed(e)
}
}
d.setCancellable {
//Cancel thread.
thread.interrupt()
}
thread.start()
}
}.finish {
it.whenSucceeded({
//Promise succeeded.
val value = it
assert(value == "Sleep succeeded.")
}, whenFailed = {
//Promise failed.
val exception = it
}) ?: run {
//Promise canceled.
}
}.submit()
By using Promises.repeat().until()
and Promises.forEach()
,
you can call asynchronous API repeatedly without callback hell and reclusive call.
by Java
Promises.repeat(new RepeatCallback<String>() {
@Override
public Deferred<String> run(RepeatParams params) throws Exception {
return doSomething(params);
}
}).until(new UntilCallback<String>() {
@Override
public boolean run(UntilParams<String> params) throws Exception {
if (params.getCancelToken().isCanceled()) {
// End of loop.
return true;
}
final Exception e = params.getException();
if(e != null && params.getIndex().incrementAndGet() <= 3){
//Retrying for error until three times.
return false;
}
//End of loop.
return true;
}
}).finish(new FinishCallback<String>() {
@Override
public void run(FinishParams<String> params) {
//The params contains last RepeatCallback result.
}
}).submit();
by Kotlin
promiseRepeat {
doSomething(it)
}.until { p ->
if(p.index.incrementAndGet() <= 3){
p.whenFailed {
//Retrying for error until three times.
return@until false
}
}
//End of loop.
true
}.finish {
//The params contains last RepeatCallback result.
}.submit()
by Java
final List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Promises.forEach(list, new AtomicInteger(), new ForEachCallback<Integer, AtomicInteger>() {
@Override
public Deferred<ForEachOp> run(ForEachParams<Integer, AtomicInteger> params) throws Exception {
params.getOperand().addAndGet(params.getValue());
return Defer.success(ForEachOp.CONTINUE);
}
}).finish(new FinishCallback<AtomicInteger>() {
@Override
public void run(FinishParams<AtomicInteger> params) {
//Calling on main thread.
if (params.getCancelToken().isCanceled()) {
//Promise is canceled.
return;
}
final Exception e = params.getException();
if (e == null) {
//Promise is failed
return;
}
//Promise is succeeded.
final int value = params.getValue().get();
assert (value == (1 + 2 + 3));
}
}).submit();
by Kotlin
val list = listOf(1, 2, 3)
promiseForEach(list, AtomicInteger(), {
defer {
it.operand.addAndGet(it.value)
ForEachOp.CONTINUE
}
}).finish {
it.whenSucceeded({
//Promise succeeded.
val value = it.get()
assert(value == 1 + 2 + 3)
}, whenFailed = {
//Promise failed.
val exception = it
}) ?: run {
//Promise canceled.
}
}.submit()
If you want to handle result on main(UI) thread,
you can do it by calling Dispatcher
class's functions.
by Java
Promises.when(new WhenCallback<String>() {
@Override
public Deferred<String> run(WhenParams params) throws Exception {
return Dispatcher.getMainDispatcher().call(new Callable<String>() {
@Override
public String call() throws Exception {
//Calling on main thread.
return "Hello Promise";
}
});
}
}).finish(new FinishCallback<String>() {
@Override
public void run(final FinishParams<String> params) {
Dispatcher.getMainDispatcher().run(new Runnable() {
@Override
public void run() {
//Calling on main thread.
if (params.getCancelToken().isCanceled()) {
//Promise is canceled.
return;
}
final Exception e = params.getException();
if (e == null) {
//Promise is failed
return;
}
//Promise is succeeded.
final String value = params.getValue();
assert(value == "Hello Promise");
}
});
}
}).submit();
by Kotlin
promiseWhen {
callOnUi {
"Hello Promise"
}
}.finish {
//Dispatch main thread.
runOnUi {
it.whenSucceeded({
//Promise succeeded.
val value = it
assert(value == "Hello Promise")
}, whenFailed = {
//Promise failed.
val exception = it
}) ?: run{
//Promise canceled.
}
}
}.submit()
If you call any blocking API on main(UI) thread,
Dispatcher.call()
has a risk of deadlock.
by Java
Promises.when(new WhenCallback<String>() {
@Override
public Deferred<String> run(WhenParams params) throws Exception {
return doSomething(params);
}
}).submitOn(YOUR_EXECUTOR);
by Kotlin
promiseWhen {
doSomething(it)
}.submitOn(YOUR_EXECUTOR)
Nested Promise can also.
by Java
Promises.when(new WhenCallback<String>() {
@Override
public Deferred<String> run(WhenParams params) throws Exception {
return Promises.when(new WhenCallback<String>() {
@Override
public Deferred<String> run(WhenParams params) throws Exception {
return doSomething(params);
}
}).getOn(YOUR_EXECUTOR, params);
}
}).submit();
by Kotlin
promiseWhen {
promiseWhen {
doSomething(it)
}.getOn(YOUR_EXECUTOR, it)
}.submit()
Promise has implementation of android.support.v4.content.Loader
.
You can use it as the Android AsyncTaskLoader
class.
See the following sample code.
Copyright 2015-2016 LoiLo inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.