Asynchronous programming
Write responsive WLJS applications with promises, callbacks, AsyncFunction, Await, and non-blocking event handlers for long-running tasks.
Asynchronous programming is a technique that enables your program to start potentially long-running tasks while remaining responsive to other events. Rather than blocking until a task finishes, your program can continue handling user input or other operations, and then process the result once the task completes.
Handling user interaction
An introductory guide to handling user interaction
Event objects and handlers
A reference and guide on basic event handling
Promise
An object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value
The WLJS ecosystem uses asynchronous programming as the primary technique for handling user interactions and polling long-running tasks. A common pattern looks like this:
Button["Press me", foo[]]Or more explicitly:
EventHandler[InputButton["Press me"], foo]Here, a button can be pressed at any time and generate an event captured by a handler function. The foo[] function (or a DownValue of the foo symbol) must be a non-blocking expression; otherwise, it will hang the evaluation kernel.
For example, foo cannot call Input, NotebookRead, SystemInputDialog, Rasterize, FrontFetch, WaitAll, or similar blocking functions — which are explicitly marked as blocking in their documentation pages.
Microtasks
Wolfram Kernel has some sort of event loop serving all external events and timers.
To scheldule something to run after the current execution use SessionSubmit:
x = 0;
EventHandler[InputButton["Press me"], Function[Null,
SessionSubmit[x=x+1]; (* non blocking, x is still 0 *)
Echo[x]; (* do something else *)
]]Asynchronous functions
Consider a scenario where the user wants to create a cell with content provided through a series of dialog windows, triggered by a button press:
n = EvaluationNotebook[];
EventHandler[InputButton["Add cell"], Function[Null,
Then[ChoiceDialogAsync["Create a cell?", "Notebook"->n], Function[choice,
If[choice =!= True, Return[]];
Then[InputStringAsync["Enter the content", "Notebook"->n], Function[content,
If[!StringQ[content], Return[]];
NotebookWrite[n, Cell[content, "Input"]];
] ]
]]
]]This is known as callback hell—deeply nested callbacks that become hard to read and maintain. This pattern often appears when working with animations (see Animation Framework), timers, and async parallel kernels.
To solve this problem, WLJS introduces coroutines (or async functions), similar to those found in many modern programming languages. Here's the same code rewritten using async/await syntax:
n = EvaluationNotebook[];
EventHandler[InputButton["Add cell"], AsyncFunction[Null,
Module[{choice, content},
choice = ChoiceDialogAsync["Create a cell?", "Notebook"->n];
choice = choice // Await;
If[choice =!= True, Return[Null]];
content = InputStringAsync["Enter the content", "Notebook"->n];
content = content // Await;
If[!StringQ[content], Return[Null]];
NotebookWrite[n, Cell[content, "Input"]];
];
]]The AsyncFunction and Await expressions provide syntactic sugar over Function, Promise, and chains of Then, allowing you to write asynchronous code in a sequential, readable style.
AsyncFunction returns a Promise, so it can be awaited from other async functions using Await.Comparison: Promises vs Async/Await
temperature = "Loading";
TextView[temperature // Offload]
Then[ParallelSubmitAsync[URLRead["https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41¤t=temperature_2m"]], Function[response,
temperature = ImportByteArray[response["BodyByteArray"], "RawJSON"]["current", "temperature_2m"];
]]temperature = "";
TextView[temperature // Offload]
AsyncFunction[Null, Module[{response},
temperature = "Loading";
response = ParallelSubmitAsync[URLRead["https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41¤t=temperature_2m"]] // Await;
temperature = ImportByteArray[response["BodyByteArray"], "RawJSON"]["current", "temperature_2m"];
]][]Advanced example: Branching and assignments
This example demonstrates branching, assignments, and compound expressions within async functions:
p1 = Promise[];
p2 = Promise[];
p3 = Promise[];
win = CurrentWindow[];
f = AsyncFunction[Null, With[{},
Speak["Press p1", "Window"->win];
p1 // Await;
Speak["Press p2A or p2B", "Window"->win];
Module[{m = -100},
m = Await[p2];
Speak["Pause 2 seconds", "Window"->win];
PauseAsync[2] // Await;
If[m > 4,
Speak["Press p3", "Window"->win];
m = Await[p3];
];
StringTemplate["Result: ``"][m]
]
]];
Then[f[], Speak];To simulate async events, use these buttons:
Button["p1", EventFire[p1, Resolve, True]]
Button["p2A", EventFire[p2, Resolve, 1]]
Button["p2B", EventFire[p2, Resolve, 5]]
Button["p3", EventFire[p3, Resolve, 10]]Await-aware control flow
If, Which, and Switch can suspend while evaluating their conditions, selectors, or selected branches. Unselected branches remain unevaluated.
result = AsyncFunction[Null,
Which[
ChoiceDialogAsync["Enter the first value?"] // Await,
InputStringAsync["First value"] // Await,
ChoiceDialogAsync["Enter the second value?"] // Await,
InputStringAsync["Second value"] // Await,
True,
Return[$Canceled]
]
][] // WaitAllAn awaited value can also be used as a Switch selector:
AsyncFunction[Null,
Switch[ChoiceDialogAsync["Continue?"] // Await,
True, "continue",
False, "cancelled",
_, Return[$Failed]
]
][] // WaitAllReturn[value] and Return[] stop the surrounding AsyncFunction, even after one or more Await expressions. Expressions following the return are not evaluated.
Async loops
Use WhileAsync when the loop condition or body needs to await a promise. It evaluates iterations serially and supports Break[], Continue[], and Return[value].
AsyncFunction[Null, Module[{attempt = 0, answer},
WhileAsync[attempt < 5,
attempt++;
answer = InputStringAsync["Enter a value or type quit"] // Await;
If[answer === "", Continue[]];
If[answer === "quit", Break[]];
If[!StringQ[answer], Return[$Canceled]];
Echo[answer];
];
attempt
]][] // WaitAllFor bounded iteration, use TableAsync or DoAsync. Their iterator bounds and ranges can be supplied through variables, including variables localized by Module:
Module[{low = 1.0, high = 3.0, step = 0.5},
TableAsync[
PauseAsync[0.1] // Await;
i
, {i, low, high, step}]
] // WaitAllNative While, For, Do, Table, and Map are not rewritten to suspend across Await; use their async counterparts or assign awaited values before entering native constructs.
Async pause
Use PauseAsync to introduce delays in async code without blocking the kernel.
Finding async functions
Check the symbol reference section for functions ending in *Async.