async
Async operations for Crystal, taking inspiration from Python's asyncio.Future
, JavaScript Promises, and async
/await
.
Installation
-
Add the dependency to your
shard.yml
:dependencies: async: github: protoncr/async
-
Run
shards install
Usage
Futures
Async contains futures, much like you'd find in Python's asyncio
. Futures by default are not executed on creation, but are rather a building block to be used for other async operations.
require "async"
# This is an empty future. Doesn't do much right now.
future = Async::Future(Int32).new
# => #<Async::Future(Int32):0x1016f2f50 ...>
future.state
# => Idle
# To put the future to use, we can give it a callback
future.callback = -> { sleep 5; 5 }
# The future still hasn't executed yet. To make it execute, we can
# call the `#execute` method
future.execute
# This will cause the callback to execute in the backround, allowing
# other tasks to happen concurrently. To wait for the execution to
# finish and retrieve the value, call `#wait`
future.wait
# => 5
Futures are made a bit more powerful when you use the Future.execute
constructor:
future = Async::Future.execute { sleep 5; 5 }
future.wait
# => 5
Futures also have built in error handling. If an error occurs while your callback is executing, the exception will be stored in the error
property and failure?
will be true. If you want to throw a possible exception during wait, you can do so with wait!
.
future = Async::Future.execute { sleep 5; 5_u8 + 251 }
future.wait!
# => Unhandled exception: Arithmetic overflow (OverflowError)
.all
/ .any
/ .race
Insipred by the JavaScript promise methods with the same names, the .all
, .any
, and .race
methods can be used to wait for certain things to happen with your futures.
.all
accepts N futures and will wait for them all to finish before returning. The return value of .all
is an Array(T)
where T
is the type(s) of the futures. If ordered
is set to true, futures will be resolved and returned in order.
.any
accepts N futures and returns the value of the first future to complete successfully.
.race
is similar to .all
, but it returns the first time a future finishes, whether successful or not.
Example:
fut1 = Async::Future.execute do
loop do
num = rand(1..99)
sleep num
puts "f1 " + num.to_s
break if num == 69
end
end
fut2 = Async::Future.execute do
loop do
num = rand(1..99)
sleep num
puts "f2 " + num.to_s
break if num == 69
end
end
Async::Future.all(fut1, fut2)
async
/ await
Async also includes the loved and hated async
/ await
.
The async
macro basically works by wrapping the body of your function in a Future
and executing that future immediately. Theoretically most functions should work just fine as async functions, but more complex function definitions are still untested. Most specifically functions with blocks.
await
can be used on async methods and futures to wait for the return value. All it really does currently is call .wait!
. If an exception is raised inside the future await
will raise an Async::UncaughtException
which contains the uncaught exception.
These are optional (so as not to pollute the global namespace with unwanted macros), so if you want to use it be sure to require future/macros
.
Contributing
- Fork it (https://github.com/protoncr/async/fork)
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request
Contributors
- Chris Watson - creator and maintainer