Async

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.*
import arrow.syntax.either.*

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 MonadSuspend.

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())
    })
  }

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.

Syntax available inside Monad Comprehensions

All the syntax functions are geared towards using Async inside Monad Comprehension to create blocks of code to be run asynchronously.

binding#bindAsync

Runs a function parameter in the Async passed as a parameter, and then awaits for the result before continuing the execution.

Note that there is no automatic error handling or wrapping of exceptions.

IO.monad().binding {
  val a = bindAsync(IO.async()) { fibonacci(100) }
  a + 1
}.fix().unsafeRunSync()

binding#bindAsyncUnsafe

Runs a function parameter in the Async passed as a parameter, and then awaits for the result before continuing the execution.

While there is no wrapping of exceptions, the left side of the Either represents an error in the execution.

IO.monad().binding {
  val a = bindAsync(IO.async()) { fibonacci(100).left() }
  a + 1
}.fix().unsafeRunSync()
IO.monad().binding {
  val a = bindAsync(IO.async()) { RuntimeException("Boom").right() }
  a + 1
}.fix().unsafeRunSync()

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 datatypes in Arrow provide instances that adhere to the Async typeclass.