A collections of extension functions to make the JVM Future, CompletableFuture, ListenableFuture API more functional and Kotlin like.

Overview

kotlin-futures

A collections of extension functions to make the CompletableFuture API more functional and Kotlin like.

[

Table of Contents

Motivation

Having worked in Scala for some time and specifically using their Future API and then going back to Kotlin a language I ❤️ ❤️ , I came to realize that the CompletableFuture API seems odd for defining and composing async operations.

Every time I use the CompletableFuture API I find myself going back to the documentation to double check what a given function would do.

Now this might be a matter of taste, but being heavily inspired by Scala's Future API, I decided to make this library to hopefully make the CompletableFuture API more functional and kotlin like

To achieve this I didn't want to introduce a new Future type and have to change any project to use the new Future type, hence by using extension functions and inlining we can have better API without any extra cost.

Download

Gradle

repositories {
    ...
    maven { url 'https://jitpack.io' }
}

dependencies {

    // for completable future
    compile 'com.github.vjames19.kotlin-futures:kotlin-futures-jdk8:<version>'

    // for listenable future
    compile 'com.github.vjames19.kotlin-futures:kotlin-futures-guava:<version>'
}

Maven

<repositories>
 <repository>
     <id>jitpack.io</id>
     <url>https://jitpack.io</url>
 </repository>
</repositories>

 <dependency>
    <groupId>com.github.vjames19</groupId>
    <artifactId>kotlin-futures</artifactId>
    <version>version</version>
</dependency>

For the rest: https://jitpack.io/#vjames19/kotlin-futures/

How to use

Every single operation accepts an Executor as its argument, by default it uses the ForkJoinPool.

For IO / blocking operations you should specify your own.

Creation

Creating a Future that runs on a given Executor (by default its the ForkJoinExecutor)

import io.github.vjames19.futures.jdk8.ForkJoinExecutor
import io.github.vjames19.futures.jdk8.Future
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executors

val future: CompletableFuture<Int> = Future { 10 }

// ForkJoinExecutor its just an alias ForkJoinPool.commonPool()
val futureOnForkJoin = Future(ForkJoinExecutor) { 10 }

val myExecutor = Executors.newSingleThreadExecutor()
val futureWithCustomExecutor = Future(myExecutor) {
    10
}

vs

val future: CompletableFuture<Int> = CompletableFuture.supplyAsync { 10 }

// ForkJoinExecutor its just an alias ForkJoinPool.commonPool()
val futureOnForkJoin = CompletableFuture.supplyAsync(Supplier { 10 }, ForkJoinExecutor)

val myExecutor = Executors.newSingleThreadExecutor()
val futureWithCustomExecutor = CompletableFuture.supplyAsync(Supplier { 10 }, myExecutor)

Creating immediate futures that run on the given thread.

import io.github.vjames19.futures.jdk8.ImmediateFuture
import io.github.vjames19.futures.jdk8.toCompletableFuture
import java.util.concurrent.CompletableFuture

val future: CompletableFuture<String> = ImmediateFuture { "Hello" }

val anotherImmediateFuture = "Hello".toString()

val aFailedImmediateFuture = IllegalArgumentException().toCompletableFuture<String>()

val futureWithTypeInference: CompletableFuture<String> = IllegalArgumentException().toCompletableFuture()

vs

val future: CompletableFuture<String> = CompletableFuture.completedFuture("Hello")

val aFailedImmediateFuture = CompletableFuture<String>().apply { completeExceptionally(IllegalArgumentException()) }

Composition

map

Map allows you to transform the success of this future into another future.

val future: CompletableFuture<String> = Future { 10 }.map { "Hello user with id: $it" }

vs

val future: CompletableFuture<String> = Future { 10 }
        .thenApplyAsync(Function { userId -> "Hello user with id: $userId" }, ForkJoinExecutor)

flatMap

flatMap allows you to do sequential composition. Creating a new future dependent on another one.

import io.github.vjames19.futures.jdk8.*
import java.util.concurrent.CompletableFuture


data class User(val id: Long, val name: String)
data class Post(val id: Long, val content: String)
data class UserPosts(val user: User, val posts: List<Post>)
fun fetchUser(id: Long): CompletableFuture<User> = Future { User(1, "Victor")}
fun fetchPosts(user: User): CompletableFuture<List<Post>> = Future { emptyList<Post>() }


// Fetching the posts depends on fetching the User
val posts = fetchUser(1).flatMap { fetchPosts(it) }

// Fetching both the user and the posts and then combining them into one
val userPosts =  fetchUser(1).flatMap { user ->
    fetchPosts(user).map { UserPosts(user, it) }
}

vs

val posts: CompletableFuture<List<Post>> = fetchUser(1)
        .thenComposeAsync(Function { fetchPosts(it) }, ForkJoinExecutor)

val userPosts: CompletableFuture<UserPosts> =  fetchUser(1)
        .thenComposeAsync(Function { user ->
            fetchPosts(user).thenApplyAsync(Function { posts ->
                UserPosts(user, posts)
            }, ForkJoinExecutor)
        }, ForkJoinExecutor)

flatten

flatten removes a level from a nested future.

import io.github.vjames19.futures.jdk8.*
import java.util.concurrent.CompletableFuture


val nestedFuture = Future { Future { 10 } }
val flattened: CompletableFuture<Int> = nestedFuture.flatten()

filter

filter will convert this future to a failed future if it doesn't match the predicate.

import io.github.vjames19.futures.jdk8.*

val future = Future { 10 }

// This future will succeed
val success = future.filter { it % 2 == 0 }

// This future will throw NoSuchElementException
val failed = future.filter { it % 3 == 0 }

zip

zip allows you to combine two futures and apply a transformation to it

import io.github.vjames19.futures.jdk8.*

val idFuture = Future { 10 }
val nameFuture = Future { "Victor" }

val helloFuture = nameFuture.zip(idFuture) { name, id -> "Hello $name with id $id" }

vs

val helloFuture: CompletableFuture<String> = nameFuture
        .thenCombineAsync(idFuture, BiFunction { name, id -> "Hello $name with id $id" }, ForkJoinExecutor)

Error Handling

The CompletableFuture API on its error handling callbacks it returns the exception wrapped into a CompletionException, I found this cumbersome to deal with and instead I'm returning the actual cause to all the error handling methods provided.

recover

recover allows you to map an exception into a value you can recover from.

import io.github.vjames19.futures.jdk8.*

val failed = Future<String> { throw IllegalArgumentException() }

val recovered = failed.recover { "recovered" }

val recoveredOnlyWhenYouCanHandleTheException = failed.recover {
    if (it is NoSuchElementException) "recovered"
    else throw it
}

vs

val recoveredOnlyWhenYouCanHandleTheException = failed.exceptionally {
    // unwrap the CompletionException
    val throwable = it.cause ?: it
    
    if (throwable is NoSuchElementException) "recovered"
    else throw throwable
}

recoverWith

recoverWith allows to recover with a new CompletableFuture

import io.github.vjames19.futures.jdk8.*

val failed = Future<String> { throw IllegalArgumentException() }

val recovered = failed.recoverWith { Future { "recovered" } }

val recoveredOnlyWhenYouCanHandleTheException = failed.recoverWith {
    if (it is NoSuchElementException) Future { "recovered" }
    else throw it
    // else it.toCompletableFuture()
}

mapError

mapError allows you only to transform the error types you are interested

import io.github.vjames19.futures.jdk8.*

val failed = Future<String> { throw IllegalArgumentException() }
        .mapError { e: IllegalArgumentException ->
            // handle the IllegalArgumentException here and return a more pertinent exception
        }

fallbackTo

fallbackTo fallbacks to the specified future, in the event that the original future fails

import io.github.vjames19.futures.jdk8.*

val failed = Future<String> { throw IllegalArgumentException() }

val fallbacked = failed.fallbackTo{ Future { "recovered" } }

val success = Future { 10 }

// Keeps the value 10
val keepsTheOriginalValue = success.fallbackTo { Future { 20 } }

Callbacks

onSuccess

onSuccess only gets called when the future is successful

import io.github.vjames19.futures.jdk8.*

val future = Future<String> { TODO() }

val s = future.onSuccess { result -> 
    // do something with the result
}

onFailure

onFailure only gets called when the future fails

import io.github.vjames19.futures.jdk8.*

val future = Future<String> { TODO() }

val s = future.onFailure { throwable -> 
    // do something with the error
}

onComplete

onComplete allows you to register callbacks for both onFailure and onSuccess

import io.github.vjames19.futures.jdk8.*

val future = Future<String> { throw IllegalArgumentException() }

val f = future.onComplete(
        onFailure = { throwable ->
            // do something with the error
        },
        onSuccess = { result -> 
            // do something with the result
        })

vs

val f = future.whenCompleteAsync(BiConsumer({ result, throwable ->
    if (throwable != null) {
        // do something with the error
    } else {
        // do something with the result
    }
}), ForkJoinExecutor)

Tests

In the tests you can find more example as to how to use a given operator.

./gradlew test
You might also like...
Yaspeller-kt - Asynchronous Yandex.Speller API wrapper for Kotlin/JVM.

yaspeller-kt Asynchronous Yandex.Speller API wrapper for Kotlin/JVM. Installation repositories { maven { url 'https://jitpack.io' }

ListUtil.kt ListUtils - Advance list functions from kotlin standard library
ListUtil.kt ListUtils - Advance list functions from kotlin standard library

ListUtils - Advance list functions from kotlin standard library A set of utility functions to work with lists in a robust way. It is based on a patter

A injection minecraft cheat using jvm attach api

Luminous A injection minecraft cheat using jvm attach api Website: https://lumi.getfdp.today Build We used a thing called Wrapper to make development

Create an application with Kotlin/JVM and Kotlin/JS, and explore features around code sharing, serialization, server- and client
Create an application with Kotlin/JVM and Kotlin/JS, and explore features around code sharing, serialization, server- and client

Practical Kotlin Multiplatform on the Web 본 저장소는 코틀린 멀티플랫폼 기반 웹 프로그래밍 워크숍(강좌)을 위해 작성된 템플릿 프로젝트가 있는 곳입니다. 워크숍 과정에서 코틀린 멀티플랫폼을 기반으로 프론트엔드(front-end)는 Ko

Create an application with Kotlin/JVM and Kotlin/JS, and explore features around code sharing, serialization, server- and client
Create an application with Kotlin/JVM and Kotlin/JS, and explore features around code sharing, serialization, server- and client

Building a Full Stack Web App with Kotlin Multiplatform 본 저장소는 INFCON 2022에서 코틀린 멀티플랫폼 기반 웹 프로그래밍 핸즈온랩을 위해 작성된 템플릿 프로젝트가 있는 곳입니다. 핸즈온 과정에서 코틀린 멀티플랫폼을

Run Kotlin/JS libraries in Kotlin/JVM and Kotlin/Native programs

Zipline This library streamlines using Kotlin/JS libraries from Kotlin/JVM and Kotlin/Native programs. It makes it possible to do continuous deploymen

Joker-App - List application tha make requests to a Chuck Norris Api
Joker-App - List application tha make requests to a Chuck Norris Api

Joker App About • Technologies • Features • Author • License 📚 About the Projec

A set of extension properties on Int, Long, Double, and Duration, that makes it easier to work with Kotlin Duration

Kotlin Duration Extensions Gradle Groovy repositories { mavenCentral() } implementation 'com.eygraber:kotlin-duration-extensions:1.0.1' Kotlin rep

Use Flink's Stateful Functions as a control-plane technology for operating a streaming-platform
Use Flink's Stateful Functions as a control-plane technology for operating a streaming-platform

statefun-ops Use 🌰 Flink Stateful Functions as a control-plane technology for operating a streaming-platform based on Apache Kafka. Walkthrough Ensur

Comments
  • Update dependecies

    Update dependecies

    Upgrade the dependencies to the newest Kotlin version. Current dependencies:

    +--- com.github.vjames19.kotlin-futures:kotlin-futures-jdk8:0.4.0 | --- org.jetbrains.kotlin:kotlin-stdlib-jre8:1.1.2-5 | --- org.jetbrains.kotlin:kotlin-stdlib-jre7:1.1.2-5 | --- org.jetbrains.kotlin:kotlin-stdlib:1.1.2-5

    ToDo:

    1. Update Kotlin dependency to version 1.2.51
    2. Change "jre" dependencies to "jdk" ("jre" seems to be deprecated).

    Great lib, thanks!

    opened by brookman 2
  • kotlin-stdlib-common Vulnerabilities

    kotlin-stdlib-common Vulnerabilities

    Run Dependency Check plugin on my Android library which uses version 1.2.0 of this library. The plugin provides a report of vulnerabilities based on the National Vulnerability Database (NVD) hosted by NIST. It listed three on the specific version of kotlin-stdlib-common this library uses that were over 8 on the CVSS score level.

    kotlin-stdlib-common-1.2.60.jar (pkg:maven/org.jetbrains.kotlin/[email protected], cpe:2.3:a:jetbrains:kotlin:1.2.60:*:*:*:*:*:*:*) : CVE-2019-10101, CVE-2019-10102, CVE-2019-10103
    

    All should be resolved just updating to latest version 1.4.20-M1

    As a workaround I'm forcing the library to use the latest version:

    dependencies {
     configurations.all {
            resolutionStrategy {
                force 'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.20-M1'
            }
        }
     ...
    }
    
    opened by malenalbc 0
Releases(1.2.0)
Owner
Victor J Reventos
Senior Software Engineer @ KyotoCooling
Victor J Reventos
A library provides some useful kotlin extension functions

ktext ?? A library provides some useful kotlin extension functions. Including in your project Gradle Add below codes to your root build.gradle file (n

热心市民苏苏仔 76 Oct 26, 2022
A powerful Minecraft Server Software coming from the future

Mirai A powerful Minecraft Server Software coming from the future Mirai is ❗ under heavy development ❗ and contributions are welcome! Features 30% fas

Etil 354 Dec 30, 2022
A collection of small utility functions to make it easier to deal with some otherwise nullable APIs on Android.

requireKTX requireKTX is a collection of small utility functions to make it easier to deal with some otherwise nullable APIs on Android, using the sam

Márton Braun 82 Oct 1, 2022
Muhammad Ariananda 7 Jul 17, 2022
Functional Kotlin & Arrow based library for generating and verifying JWTs and JWSs

kJWT Functional Kotlin & Arrow based library for generating and verifying JWTs and JWSs. JWS JWT The following Algorithms are supported: HS256 HS384 H

Peter vR 31 Dec 25, 2022
Functional Constructs for Databinding + Kotlin + RxJava

ObservableFlow Pt 1/3 Pt 2/3: Stepper Indicator Pt 3/3: SugarPreferences Functional Kotlin constructs like map(), filter() and 12 more functions built

Rakshak R.Hegde 26 Oct 3, 2022
Exercises for Functional Programming learning in Kotlin with Arrow

Exercises for Functional Programming in Kotlin with Arrow-kt Exercises and practice code for Functional Programming learning in Kotlin with Arrow Requ

Jack Lo 3 Nov 11, 2021
Ivy FRP is a Functional Reactive Programming framework for declarative-style programming for Android

FRP (Functional Reactive Programming) framework for declarative-style programming for Andorid. :rocket: (compatible with Jetpack Compose)

null 8 Nov 24, 2022
ZoomHelper will make any view to be zoomable just like Instagram pinch-to-zoom

ZoomHelper ZoomHelper will make any view to be zoomable just like the Instagram pinch-to-zoom. ?? Installation ZoomHelper is available in the JCenter,

AmirHosseinAghajari 238 Dec 25, 2022
🔎 An HTTP inspector for Android & OkHTTP (like Charles but on device) - More Chucker than Chuck

Chucker A fork of Chuck Getting Started Features Multi-Window Configure Redact-Header️ Decode-Body Migrating Snapshots FAQ Contributing Building Ackno

Chucker Team 2.9k Dec 30, 2022