promise
Crystal Lang Promises
Provides a fully typed implementation of promises for crystal lang.
A word of advice
It's good practice to only expose synchronous APIs in crystal lang.
If you are building a library that uses promises internally, make sure to call #get
when returning on public interfaces.
Overview
A promise represents the eventual result of an operation. You can use a promise to specify what to do when an operation eventually succeeds or fails.
require "promise"
# A promise defines the eventual type that will be returned
promise = Promise.new(String)
# You can then perform some asynchronous action that will resolve the promise
channel.send {promise, arg1, arg2}
# A callback can be used to access the result.
# All return values are nilable
promise.then do |result|
puts result
end
# You can also handle Exceptions.
# All failure responses are of type Exception
promise.catch do |error|
puts error.message
end
# If you don't care about the result but want to be notified of job completion
promise.finally do |error|
if error
job_failed
else
job_success
end
end
# You can also pause execution and wait for the value
# This will raise an error if the promise was rejected
begin
result = promise.get
rescue error
puts error.message
end
Simple concurrency control
Promise.defer
is similar to spawn
, however you can wait for it to complete with a value or error.
The promise type is inferred from the return type of the defer block.
Promise.defer {
# your concurrent code here, in spawned fiber
}.then { |result|
# process returned value
}.catch { |error|
# handle errors
}
Promise.all
The Promise.all(Enumerable)
method returns a single Promise that resolves when all of the promises in the argument(s) have resolved or when the argument contains no promises.
It rejects with the reason of the first promise that rejects.
# synchronous response
value1, value2 = Promise.all(
Promise.defer { function1 },
Promise.defer { function2 }
).get
# using callbacks
Promise.all(
Promise.defer { function1 },
Promise.defer { function2 }
).then do |results|
results.each { }
end
There are no restrictions placed on return types so function1 can return a String and function2 an Int32, for example.
Promise.race
The Promise.race(Enumerable)
method returns a promise that resolves or rejects as soon as one of the promises resolves or rejects, with the value or reason from that promise.
It will raise an error if no promises are provided.
Promise.race(
Promise.defer { sleep rand(0.0..1.0); "p1 wins" },
Promise.defer { sleep rand(0.0..1.0); "p2 wins" }
).get
Promise.map
Promise also support asynchronous maps over Enumerable types, yielding an array of the resolved elements.
Promise.map([1, 2, 3]) do |x|
sleep x
x
end.sum # => 6
Multiple receivers
There might be multiple parties interested in the results of an operation. Promises ensure all receivers receive the result when the operation is complete.
operation = Promise.defer {
# your concurrent code here, in spawned fiber
}
operation.then do |result|
puts "log the result #{result}"
end
operation.then do |result|
email(result.contacts) if result.state == :not_good
end
Of course new receivers will probably be added dynamically at runtime
Promise chaining
Promises can be chained together and the return type of every .then
block is used to build the next promise, so it is simple to transform values.
Promise.defer {
# obtains the age of an imaginary person
get_age
}.catch { |error|
if error.message == "not born"
# We can recover the operation by returning a compatible value
0
else
raise error
end
}.then { |age|
# we can perform another async operation
Promise.defer {
print_birthday_card(age)
}
}.then {
log_success
}.catch { |error|
log_error(error)
}.finally {
print_next_card
}