-
Notifications
You must be signed in to change notification settings - Fork 1
Async Await
astal-lua provides the async and await decorators to simplify asynchronous I/O operations, allowing functions to run in async-enabled contexts. Internally, they rely on Gio.Async, which transforms Gio-style asynchronous functions into synchronous-like code.
In lgi, many async operations follow a callback-based pattern:
-
name_asyncstarts the operation and registers a callback. -
name_finishis called inside the callback to retrieve the result.
While common in Gio, this can lead to deeply nested callback chains (callback hell). Gio.Async allows you to wrap these functions using Lua coroutines, so you can write linear, non-blocking code.
The async decorator wraps a function to accept a callback. The function inside does not return a value, so the result must be handled via the callback, similar to Gio’s _async functions.
---@generic F: function
---@param fn F
---@return F
local async = function(fn)
return function(...) return Gio.Async.start(fn)(...) end
end---@async
local async_read_file = function(filename)
local file = Gio.File.new_for_path(filename)
local info = file:async_query_info('standard::size', 'NONE')
local stream = file:async_read()
local bytes = stream:async_read_bytes(info:get_size())
assert(stream:async_close())
return bytes.data
end
local read_file_async = async(function(filename, callback)
callback(async_read_file(filename))
end)
read_file_async('/etc/passwd', print) -- workslocal function ExampleButton()
return Widget.Button {
label = 'Dump!',
on_clicked = async(function()
local contents = async_read_file('/etc/passwd')
print(contents)
end),
}
endWarning
Calling an async_name function outside an async-enabled context will not work.
The await decorator allows calling async functions and returning their result directly, without using callbacks. The parent function must be executed in an async-enabled context (---@async).
---@generic F: function
---@param fn F
---@return F
local await = function(fn)
return function(...) return Gio.Async.call(fn)(...) end
endUnlike async, which schedules a function in the background, await blocks only the coroutine, not the main thread, keeping the UI responsive.
local read_file = await(function(path)
return async_read_file(path)
end)
local function OtherExampleButton()
return Widget.Button {
label = 'DumpAgain!',
on_clicked = function()
local contents = read_file('/etc/passwd') -- This wont block the UI
print(contents)
end,
}
endWarning
Do not call an await function inside another async function via a wrapper. For example:
local async_fn = function() end
local fn = await(function() return async_fn() end)
local example = async(function()
local result = fn() -- ❌ Don't do this
end)Instead, call the original async function directly:
local example = async(function()
local result = async_fn() -- ✅ Correct
end)Tip
Some libraries expose asynchronous methods through corresponding _finish functions. Even if a method does not have an _async suffix, you
can inspect it to determine whether it can be used from async-enabled functions.
A quick way to check is:
print(MyClass.my_function)If a matching *_finish method exists internally, the function may support asynchronous invocation patterns.
local Pam = lgi.require("AstalAuth").Pam
print(Pam.authenticate) -- valid method
print(Pam.async_authenticate) -- valid async method
-- Pam.authenticate_finish also exists internally
-- which indicates async support through the Gio.Async pattern.This is particularly useful when working with GObject-based libraries, where asynchronous APIs are often implemented as method() + method_finish() pairs, even when the public API naming does not strictly follow a *_async convention.