taskle
Types
pub type Error {
Timeout
Crashed(reason: String)
NotOwner
NotReady
}
Constructors
-
Timeout
The task didn’t complete within the specified timeout.
-
Crashed(reason: String)
The task process crashed with the given reason.
-
NotOwner
Attempted to await or cancel a task from a different process than the one that created it.
-
NotReady
The task is not ready yet (used by yield function).
A task represents an asynchronous computation running in a separate BEAM process.
Tasks are created with async
and their results are retrieved with await
.
Only the process that created a task can await its result or cancel it.
pub opaque type Task(a)
pub type TaskMessage(a) {
TaskResult(value: a)
}
Constructors
-
TaskResult(value: a)
Values
pub fn all_settled(
tasks: List(Task(a)),
timeout: Int,
) -> Result(List(SettledResult(a)), Error)
Waits for all tasks to complete regardless of success or failure (analog of Promise.allSettled).
Returns the results of all tasks, whether they succeeded or failed. Unlike try_await_all
,
this function never cancels tasks early - it waits for all tasks to complete.
Examples
let task1 = taskle.async(fn() { 42 })
let task2 = taskle.async(fn() {
// This will crash
panic as "oops"
})
let task3 = taskle.async(fn() { "hello" })
case taskle.all_settled([task1, task2, task3], 5000) {
Ok([Fulfilled(42), Rejected(Crashed("oops")), Fulfilled("hello")]) -> {
io.println("All tasks completed")
}
Error(taskle.Timeout) -> io.println("Some tasks timed out")
}
pub fn async(fun: fn() -> a) -> Task(a)
Creates an asynchronous task that runs the given function in a separate process.
The task is unlinked from the calling process, meaning that if the task crashes, it won’t cause the calling process to crash. Only the process that creates the task can await its result or cancel it.
Examples
let task = taskle.async(fn() {
// This runs in a separate process
process.sleep(1000)
42
})
pub fn async_unlinked(fun: fn() -> a) -> Task(a)
Creates an asynchronous task that is not linked to the calling process.
This is identical to async
, but provided for clarity when you specifically
want to emphasize that the task is unlinked. The task DOES send its result back
and can be awaited, unlike tasks created with start
.
Key difference from start
: Tasks created with async_unlinked
send their
result back and can be awaited. Use this when you need the result but want to
emphasize the unlinked nature, or when you might await the task later.
Examples
let task = taskle.async_unlinked(fn() {
// This task won't affect the parent process if it crashes
expensive_computation()
})
// You can still await the result
case taskle.await(task, 5000) {
Ok(result) -> use_result(result)
Error(_) -> handle_error()
}
pub fn await(task: Task(a), timeout: Int) -> Result(a, Error)
Waits for a task to complete with a timeout in milliseconds.
Only the process that created the task can await its result. If called from
a different process, returns Error(NotOwner)
.
Examples
case taskle.await(task, 5000) {
Ok(value) -> io.println("Success: " <> int.to_string(value))
Error(taskle.Timeout) -> io.println("Task timed out after 5 seconds")
Error(taskle.Crashed(reason)) -> io.println("Task failed: " <> reason)
Error(taskle.NotOwner) -> io.println("Cannot await task from different process")
}
pub fn await2(
task1: Task(t1),
task2: Task(t2),
timeout: Int,
) -> Result(#(t1, t2), Error)
Waits for two tasks to complete and returns a tuple of their results.
Both tasks must complete successfully within the timeout, otherwise an error is returned. If any task fails, all remaining tasks are cancelled.
Examples
let task1 = taskle.async(fn() { 42 })
let task2 = taskle.async(fn() { "hello" })
case taskle.await2(task1, task2, 5000) {
Ok(#(num, str)) -> {
// num = 42, str = "hello"
io.debug(#(num, str))
}
Error(taskle.Timeout) -> io.println("Tasks timed out")
Error(taskle.Crashed(reason)) -> io.println("A task crashed: " <> reason)
}
pub fn await3(
task1: Task(t1),
task2: Task(t2),
task3: Task(t3),
timeout: Int,
) -> Result(#(t1, t2, t3), Error)
Waits for three tasks to complete and returns a tuple of their results.
All tasks must complete successfully within the timeout, otherwise an error is returned. If any task fails, all remaining tasks are cancelled.
Examples
let task1 = taskle.async(fn() { 42 })
let task2 = taskle.async(fn() { "hello" })
let task3 = taskle.async(fn() { True })
case taskle.await3(task1, task2, task3, 5000) {
Ok(#(num, str, bool)) -> {
// num = 42, str = "hello", bool = True
io.debug(#(num, str, bool))
}
Error(taskle.Timeout) -> io.println("Tasks timed out")
Error(taskle.Crashed(reason)) -> io.println("A task crashed: " <> reason)
}
pub fn await4(
task1: Task(t1),
task2: Task(t2),
task3: Task(t3),
task4: Task(t4),
timeout: Int,
) -> Result(#(t1, t2, t3, t4), Error)
Waits for four tasks to complete and returns a tuple of their results.
All tasks must complete successfully within the timeout, otherwise an error is returned. If any task fails, all remaining tasks are cancelled.
Examples
let task1 = taskle.async(fn() { 42 })
let task2 = taskle.async(fn() { "hello" })
let task3 = taskle.async(fn() { True })
let task4 = taskle.async(fn() { 3.14 })
case taskle.await4(task1, task2, task3, task4, 5000) {
Ok(#(num, str, bool, float)) -> {
// num = 42, str = "hello", bool = True, float = 3.14
io.debug(#(num, str, bool, float))
}
Error(taskle.Timeout) -> io.println("Tasks timed out")
Error(taskle.Crashed(reason)) -> io.println("A task crashed: " <> reason)
}
pub fn await5(
task1: Task(t1),
task2: Task(t2),
task3: Task(t3),
task4: Task(t4),
task5: Task(t5),
timeout: Int,
) -> Result(#(t1, t2, t3, t4, t5), Error)
Waits for five tasks to complete and returns a tuple of their results.
All tasks must complete successfully within the timeout, otherwise an error is returned. If any task fails, all remaining tasks are cancelled.
Examples
let task1 = taskle.async(fn() { 42 })
let task2 = taskle.async(fn() { "hello" })
let task3 = taskle.async(fn() { True })
let task4 = taskle.async(fn() { 3.14 })
let task5 = taskle.async(fn() { [1, 2, 3] })
case taskle.await5(task1, task2, task3, task4, task5, 5000) {
Ok(#(num, str, bool, float, list)) -> {
// num = 42, str = "hello", bool = True, float = 3.14, list = [1, 2, 3]
io.debug(#(num, str, bool, float, list))
}
Error(taskle.Timeout) -> io.println("Tasks timed out")
Error(taskle.Crashed(reason)) -> io.println("A task crashed: " <> reason)
}
pub fn await_forever(task: Task(a)) -> Result(a, Error)
Waits for a task to complete without a timeout.
Only the process that created the task can await its result. If called from
a different process, returns Error(NotOwner)
. Will wait indefinitely until
the task completes or crashes.
Examples
case taskle.await_forever(task) {
Ok(value) -> io.println("Success: " <> int.to_string(value))
Error(taskle.Crashed(reason)) -> io.println("Task failed: " <> reason)
Error(taskle.NotOwner) -> io.println("Cannot await task from different process")
}
pub fn cancel(task: Task(a)) -> Result(Nil, Error)
Cancels a running task.
Only the process that created the task can cancel it. If called from
a different process, returns Error(NotOwner)
.
Examples
let task = taskle.async(fn() {
process.sleep(10_000)
"done"
})
// Cancel the task
case taskle.cancel(task) {
Ok(Nil) -> io.println("Task cancelled")
Error(taskle.NotOwner) -> io.println("Cannot cancel task from different process")
}
pub fn parallel_map(
list: List(a),
fun: fn(a) -> b,
timeout: Int,
) -> Result(List(b), Error)
Processes a list of items in parallel, applying the given function to each item.
Returns when all tasks complete or when any task fails/times out. If any task fails, all remaining tasks are cancelled.
Examples
let numbers = [1, 2, 3, 4, 5]
case taskle.parallel_map(numbers, fn(x) { x * x }, 5000) {
Ok(results) -> {
// results = [1, 4, 9, 16, 25]
io.debug(results)
}
Error(taskle.Timeout) -> io.println("Some tasks timed out")
Error(taskle.Crashed(reason)) -> io.println("A task crashed: " <> reason)
}
pub fn pid(task: Task(a)) -> process.Pid
Returns the process ID of the task’s underlying BEAM process.
Useful for debugging or process monitoring.
Examples
let task = taskle.async(fn() { 42 })
let process_id = taskle.pid(task)
io.debug(process_id)
pub fn race(
tasks: List(Task(a)),
timeout: Int,
) -> Result(a, Error)
Waits for the first task to complete (analog of Promise.race).
Returns the result of the first task to complete, whether successful or failed. All other tasks are cancelled when the first one completes.
Examples
let task1 = taskle.async(fn() {
process.sleep(1000)
"slow"
})
let task2 = taskle.async(fn() {
process.sleep(100)
"fast"
})
case taskle.race([task1, task2], 5000) {
Ok("fast") -> io.println("Task2 won the race")
Error(taskle.Timeout) -> io.println("All tasks timed out")
Error(taskle.Crashed(reason)) -> io.println("First task to complete crashed: " <> reason)
}
pub fn shutdown(
task: Task(a),
timeout: Int,
) -> Result(Nil, Error)
Shuts down a task gracefully with a timeout.
This function attempts to shut down a task more gracefully than cancel
.
It first removes monitoring of the process, then kills it. If the task doesn’t shut down
within the timeout, it returns an error.
Only the process that created the task can shut it down. If called from
a different process, returns Error(NotOwner)
.
Examples
let task = taskle.async(fn() {
process.sleep(10_000)
"done"
})
// Shutdown the task with a 1 second timeout
case taskle.shutdown(task, 1000) {
Ok(Nil) -> io.println("Task shut down gracefully")
Error(taskle.Timeout) -> io.println("Task didn't shut down in time")
Error(taskle.NotOwner) -> io.println("Cannot shutdown task from different process")
}
pub fn start(fun: fn() -> a) -> Task(a)
Creates an asynchronous task for side effects that doesn’t need to be awaited.
This is useful for fire-and-forget operations where you don’t need the result. The task runs in an unlinked process and won’t affect the parent process if it crashes.
Key difference from async_unlinked
: Tasks created with start
do NOT send
their result back, making them truly fire-and-forget. While you can technically
call await
on them, it will never receive a result. Use start
for side effects
like logging, cleanup, or background processing where you don’t care about the return value.
Examples
taskle.start(fn() {
// Log something or perform side effects
io.println("Background task completed")
cleanup_temp_files()
})
pub fn try_await_all(
tasks: List(Task(a)),
timeout: Int,
) -> Result(List(a), Error)
Waits for all tasks to complete with a timeout.
Returns when all tasks complete or when any task fails/times out. If any task fails, all remaining tasks are cancelled.
Examples
let task1 = taskle.async(fn() { 42 })
let task2 = taskle.async(fn() { "hello" })
let task3 = taskle.async(fn() { True })
case taskle.try_await_all([task1, task2, task3], 5000) {
Ok([a, b, c]) -> {
// a = 42, b = "hello", c = True
io.debug([a, b, c])
}
Error(taskle.Timeout) -> io.println("Some tasks timed out")
Error(taskle.Crashed(reason)) -> io.println("A task crashed: " <> reason)
}
pub fn yield(task: Task(a)) -> Result(a, Error)
Checks if a task has completed without blocking.
Returns Ok(value)
if the task has completed, Error(NotReady)
if it’s still
running, or other errors if the task crashed or ownership check fails.
Examples
case taskle.yield(task) {
Ok(value) -> io.println("Task completed: " <> int.to_string(value))
Error(taskle.NotReady) -> io.println("Task still running")
Error(taskle.Crashed(reason)) -> io.println("Task failed: " <> reason)
Error(taskle.NotOwner) -> io.println("Cannot check task from different process")
}