WLJS LogoWLJS Notebook

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.

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

Experimental feature

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&current=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&current=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]
  ]
][] // WaitAll

An awaited value can also be used as a Switch selector:

AsyncFunction[Null,
  Switch[ChoiceDialogAsync["Continue?"] // Await,
    True, "continue",
    False, "cancelled",
    _, Return[$Failed]
  ]
][] // WaitAll

Return[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
]][] // WaitAll

For 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}]
] // WaitAll

Native 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.

On this page