Are you happy with your logging solution? Would you help us out by taking a 30-second survey? Click here


An asynchronous effect monad for PureScript

Subscribe to updates I use purescript-aff

Statistics on purescript-aff

Number of watchers on Github 176
Number of open issues 7
Average time to close an issue 2 months
Main language PureScript
Average time to merge a PR 3 days
Open pull requests 14+
Closed pull requests 5+
Last commit almost 2 years ago
Repo Created over 4 years ago
Repo Last Updated over 1 year ago
Size 375 KB
Organization / Authorslamdata
Latest Releasev4.0.2
Page Updated
Do you use purescript-aff? Leave a review!
View open issues (7)
View purescript-aff activity
View on github
Fresh, new opensource launches 🚀🚀🚀
Trendy new open source projects in your inbox! View examples

Subscribe to our mailing list

Evaluating purescript-aff for your project? Score Explanation
Commits Score (?)
Issues & PR Score (?)


Latest release Build status

An asynchronous effect monad and threading model for PureScript.


main = launchAff do
  response <- Ajax.get ""
  log response.body

Getting Started


bower install purescript-aff


An example of Aff is shown below:

deleteBlankLines path = do
  contents <- loadFile path
  let contents' = S.join "\n" $ A.filter (\a -> S.length a > 0) (S.split "\n" contents)
  saveFile path contents'

This looks like ordinary, synchronous, imperative code, but actually operates asynchronously without any callbacks. Error handling is baked in so you only deal with it when you want to.

The library contains instances for Semigroup, Monoid, Apply, Applicative, Bind, Monad, Alt, Plus, MonadEff, MonadError, and Parallel. These instances allow you to compose asynchronous code as easily as Eff, as well as interop with existing Eff code.

Escaping Callback Hell

Hopefully, you're using libraries that already use the Aff type, so you don't even have to think about callbacks!

If you're building your own library, then you can make an Aff from low-level Eff callbacks with makeAff.

makeAff :: forall eff a. ((Either Error a -> Eff eff Unit) -> Eff eff (Canceler eff)) -> Aff eff a

This function expects you to provide a handler, which should call the supplied callback with the result of the asynchronous computation.

You should also return Canceler, which is just a cleanup effect. Since Aff threads may be killed, all asynchronous operations should provide a mechanism for unscheduling it.

Control.Monad.Aff.Compat provides functions for easily binding FFI definitions:

exports._ajaxGet = function (request) { // accepts a request
  return function (onError, onSuccess) { // and callbacks
    var req = doNativeRequest(request, function (err, response) { // make the request
      if (err != null) {
        onError(err); // invoke the error callback in case of an error
      } else {
        onSuccess(response); // invoke the success callback with the reponse

    // Return a canceler, which is just another Aff effect.
    return function (cancelError, cancelerError, cancelerSuccess) {
      req.cancel(); // cancel the request
      cancelerSuccess(); // invoke the success callback for the canceler
foreign import _ajaxGet :: forall eff. Request -> EffFnAff (ajax :: AJAX | eff) Response

We can wrap this into an asynchronous computation like so:

ajaxGet :: forall eff. Request -> Aff (ajax :: AJAX | eff) Response
ajaxGet = fromEffFnAff <<< _ajaxGet

This eliminates callback hell and allows us to write code simply using do notation:

example = do
  response <- ajaxGet req
  log response.body


All purely synchronous computations (Eff) can be lifted to asynchronous computations with liftEff defined in Control.Monad.Eff.Class.

liftEff $ log "Hello world!"

This lets you write your whole program in Aff, and still call out to synchronous code.

If your Eff code throws exceptions (exception :: EXCEPTION), you can remove the exception label using liftEff'. Exceptions are part of Affs built-in semantics, so they will always be caught and propagated anyway.

Dealing with Failure

Aff has error handling baked in, so ordinarily you don't have to worry about it.

When you need to deal with failure, you have a few options.

  1. Alt
  2. MonadError
  3. Bracketing

1. Alt

Because Aff has an Alt instance, you may also use the operator <|> to provide an alternative computation in the event of failure:

example = do
  result <- Ajax.get "" <|> Ajax.get ""
  pure result

2. MonadError

Aff has a MonadError instance, which comes with two functions: catchError, and throwError.

These are defined in purescript-transformers. Here's an example of how you can use them:

example = do
  resp <- Ajax.get "" `catchError` \_ -> pure defaultResponse
  when (resp.statusCode /= 200) do
    throwError myErr
  pure resp.body

3. Bracketing

Aff threads can be cancelled, but sometimes we need to guarantee an action gets run even in the presence of exceptions or cancellation. Use bracket to acquire resources and clean them up.

example =
    (openFile myFile)
    (\file -> closeFile file)
    (\file -> appendFile "hello" file)

In this case, closeFile will always be called regardless of exceptions once openFile completes.


Using forkAff, you can fork an asynchronous computation, which means that its activities will not block the current thread of execution:

forkAff myAff

Because Javascript is single-threaded, forking does not actually cause the computation to be run in a separate thread. Forking just allows the subsequent actions to execute without waiting for the forked computation to complete.

Forking returns a Fiber eff a, representing the deferred computation. You can kill a Fiber with killFiber, which will run any cancelers and cleanup, and you can observe a Fiber's final value with joinFiber. If a Fiber threw an exception, it will be rethrown upon joining.

example = do
  fiber <- forkAff myAff
  killFiber (error "Just had to cancel") fiber
  result <- try (joinFiber fiber)
  if isLeft result
    then (log "Canceled")
    else (log "Not Canceled")


The Control.Monad.Aff.AVar module contains asynchronous variables, which are very similar to Haskell's MVar.

AVars represent a value that is either full or empty. Calling takeVar on an empty AVar will queue until it is filled by a putVar.

example = do
  var <- makeEmptyVar
  _ <- forkAff do
    value <- takeVar var
    log $ "Got a value: " <> value
  _ <- forkAff do
    delay (Milliseconds 100.0)
    putVar var "hello"
  pure unit
(Waits 100ms)
> Got a value: hello

Likewise, calling putVar on a filled AVar will queue until it is emptied by a takeVar.

example = do
  var <- makeVar "hello"
  _ <- forkAff do
    delay (Milliseconds 100.0)
    value <- takeVar var
    log $ "Got a value: " <> value
  putVar var "next"
  log "Value put"
(Waits 100ms)
> Got a value: hello
> Value put

These combinators (and a few more) can be used as the building blocks for complex asynchronous coordination.

Parallel Execution

The Parallel instance for Aff makes writing parallel computations a breeze.

Using parallel from Control.Parallel will turn a regular Aff into ParAff. ParAff has an Applicative instance which will run effects in parallel, and an Alternative instance which will race effects, returning the one which completes first (canceling the others). To get an Aff back, just run it with sequential.

-- Make two requests in parallel
example =
  sequential $
    Tuple <$> parallel (Ajax.get "")
          <*> parallel (Ajax.get "")
-- Make a request with a 3 second timeout
example =
  sequential $ oneOf
    [ parallel (Just <$> Ajax.get "")
    , parallel (Nothing <$ delay (Milliseconds 3000.0))
tvShows =
  [ "Stargate_SG-1"
  , "Battlestar_Galactica"
  , "Farscape"

getPage page =
  Ajax.get $ "" <> page

-- Get all pages in parallel
allPages = parTraverse getPage tvShows

-- Get the page that loads the fastest
fastestPage = parOneOfMap getPage tvShows

API Docs

API documentation is published on Pursuit.

purescript-aff open issues Ask a question     (View All Issues)
  • almost 3 years `peekVar` does not work properly
  • about 3 years Move as much FFI into PS as possible
  • about 3 years `attempt` doesn't handle exceptions in Aff function
  • over 3 years Imports for the readme examples?
  • over 4 years make avar puts block
  • over 4 years improve test suite
purescript-aff open pull requests (View All Pulls)
  • Add forkAll combinator
  • Removing shadowing in catch blocks.
  • Fix warnings generated by purescript-0.8.0
  • Allow cancellation of computations forked with `runAff`
  • Initial pass on catch rewriting; needs review!
  • Propagate Par errors
  • updates for 0.9
  • Fixes #23
  • Add `peekVar` for reading from an avar without consuming
  • Add JS listing and fix existing issues
  • Simplify AVar JS
  • Delete unlawful MonadPlus instance
  • Rename monadZero to monadZeroAff
  • Async Push-Pop example added
purescript-aff list of languages used
purescript-aff latest release notes
v4.0.2 v4.0.2
  • Fix regression in ParAff Applicative behavior when an exception occurs.
v4.0.1 v4.0.1
  • Fixes JavaScript runtime error in ParAff cancellation.
v4.0.0 v4.0.0

This release (v4.0.0) features a revamped API for writing more expressive asynchronous programs with stronger guarantees.

  • Fiber cooperative multi-tasking primitive for fork/join workflows.
  • Stronger cleanup guarantees with bracket and supervise.
  • Reformulated AVar semantics
  • Rewritten core with an emphasis on performance and consistency.

New Features and Enhancements


Previously, Affs supported forkAff, but it was very difficult to get values back when forked computations completed. Libraries like purescript-aff-future were written to overcome this limitation (though with limitations of their own). The semantics of purescript-aff-future have been assimilated into Aff through the Fiber type without any of the previous limitaions (like lack of cancellation). Fibers make it easy to not only fork computations, but also share their results among many consumers with joinFiber, which will wait until the Fiber completes, or yield immediately if it has already resolved. If a Fiber threw an exception, then the exception will be rethrown in the observer. Fibers additionally support cancellation (killFiber) and finalizers for cleanup (bracket).


When we kill/cancel a Fiber, often times we need to make sure some resource gets released. bracket lets you take control of the acquire/use/release resource cycle.

example =
    (openFile myFile) -- Acquire a resource
    (\file -> closeFile file) -- Release the resource
    (\file -> appendFile "hello" file) -- Use the resource

In the example above, the runtime will always ensure the release effect will run even in the presence of cancellation. There is also generalBracket, which lets you observe whether the primary action completed successfully, threw an exception, or was killed asynchronously and run different cleanup effects accordingly.


Sometimes we need to fork many Fibers for a task, but it's possible (often through cancellation) for these sub-tasks to leak. We've introduced a supervise combinator which will automatically keep track of forked Fibers and clean them up and run their finalizers once the computation completes or is cancelled.

example = supervise do
  _ <- forkAff requestA
  _ <- forkAff requestB

In the above example, if requestA or requestB are still running when requestC completes, they will be killed by the runtime and have their finalizers run.


As an alternative to forkAff, which eagerly forks and runs a computations, we've introduced suspendAff. This forks a computation but does not initiate it until a result is demanded via joinFiber. Results are still memoized (as all Fiber results are), but are just computed lazily.


With the old callback approach, each bind resulted in more and more stack, and it was trivial to blow the stack unless you explicitly used tailRecM. The Aff interpreter now uses a constant amount of stack space, making tailRecM unnecesary. This extends to ParAff as well.

Uncaught exceptions

Previously, exceptions thrown in forked computations were completely swallowed. This made it extremely difficult to diagnose bugs. Now if a Fiber has no observers and it throws an exception, the exception will always be rethrown in a fresh stack. This can be observed by things like window.onerror or just by watching the console.

Breaking Changes

  • The low-level callback representation is no longer relevant. If you've defined Aff effects via the FFI, you should transition to using Control.Monad.Aff.Compat, which provides an EffFn adapter. This makes it easy to use idiomatic JavaScript callbacks when building Aff actions.
  • The AVar API methods have changed to match Haskell's MVar API. putVar now blocks until the AVar actually assimilates the value. Previously, putVar would queue the value, but yield immediately. It's possible to recover similar behavior as the old API with forkAff (try (putVar value avar)) (though this should be considered mildly unsafe), or you can use tryPutVar which will attempt a synchronous put.
  • Argument order for AVar and Fiber operations consistently put the subject last.
  • Several unlawful instances where removed for Aff (Alternative, MonadPlus, and MonadZero).
  • forkAff now returns a Fiber rather than a Canceler.
  • forkAll was removed. Just use Traversable and forkAff directly.
  • cancel was removed. Use killFiber.
  • The signature of makeAff has changed to provide a single callback which takes an Either Error a argument. Cancelers are also required. If you are sure you have no means of cancelling an action, you can use nonCanceler or mempty.
  • Cancelers no longer yield Boolean. This was meaningless and not useful, so all cancellation effects now yield Unit.
  • ParAff is no longer a newtype. Parallel computations should be constructed via Control.Parallel with parallel and sequential.
Other projects in PureScript