Async

intermediate

Being able to run code in a different context of execution (i.e. thread) than the current one implies that, even if it’s part of a sequence, the code will have to be asynchronous. Running asynchronous code always requires a callback after completion on error capable of returning to the current thread.

The same way the typeclass Monad represents a sequence of events, and MonadError a sequence that can fail, the typeclass Async represents asynchronous code with a callback. Examples of that can run code asynchronously are typically datatypes that can suspend effects, and delay evaluation.

import arrow.*
import arrow.core.*
import arrow.effects.*

IO.async()
  .async { callback: (Either<Throwable, Int>) -> Unit ->
    callback(1.right())
  }.fix().attempt().unsafeRunSync()
// Right(b=1)
IO.async()
  .async { callback: (Either<Throwable, Int>) -> Unit ->
    callback(RuntimeException().left())
  }.fix().attempt().unsafeRunSync()
// Left(a=java.lang.RuntimeException)

Async includes all combinators present in MonadDefer.

Main Combinators

async

Receives a function returning Unit with a callback as a parameter. The function is responsible of calling the callback once it obtains a result. The callback accepts Either<Throwable, A> as the return, where the left side of the Either represents an error in the execution and the right side is the completion value of the operation.

IO.async()
  .async { callback: (Either<Throwable, Int>) -> Unit ->
    userFetcherWithCallback("1").startAsync({ user: User ->
      callback(user.left())
    }, { error: Exception ->
      callback(error.right())
    })
  }
IO.async()
  .async { callback: (Either<Throwable, Int>) -> Unit ->
    userFromDatabaseObservable().subscribe({ user: User ->
      callback(user.left())
    }, { error: Exception ->
      callback(error.right())
    })
  }

continueOn

It makes the rest of the operator chain to be executed on a separate CoroutineContext, effectively jumping threads if necessary.

IO.async().run {
  // In current thread
  just(createUserFromId(123))
    .continueOn(CommonPool)
    // In CommonPool
    .flatMap { request(it) }
    .continueOn(Ui)
    // In Ui
    .flatMap { showResult(it) }
}

Behind the scenes continueOn() starts a new coroutine and passes the rest of the chain as the block to execute.

never

Creates an object using async() whose callback is never called.

Depending on how the datatype is implemented this may cause unexpected errors like awaiting infinitely for a result.

Use with SEVERE CAUTION.

IO.async()
  .never()
  .unsafeRunSync()
// ERROR!! The program blocks the current thread forever.

never() exists to test datatypes that can handle non-termination. For example, IO has unsafeRunTimed that runs never() safely.

Laws

Arrow provides AsyncLaws in the form of test cases for internal verification of lawful instances and third party apps creating their own Async instances.

Data Types

The following data types in Arrow provide instances that adhere to the Async type class.