Async Support
Transpose supports async and await, backed by the JavaScript event loop. C#
async methods compile to native JavaScript async functions, and
System.Threading.Tasks.Task is the runtime's own task type, adapted to and from
native promises at the boundaries.
Async/await
Write asynchronous code exactly as you would in .NET:
public async Task<string> FetchDataAsync(string url)
{
var response = await Http.GetAsync(url);
return response.Text;
}
public static async Task Main()
{
try
{
string data = await FetchDataAsync("https://api.example.com/data");
Console.WriteLine(data);
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message);
}
}
An async Task Main entry point is supported: the runtime awaits it and surfaces
a faulted result rather than dropping it.
How it works
Transpose does not rewrite an async method into a state machine. It emits a
native JavaScript async function and lets the engine drive it:
- The body of a C#
asyncmethod is emitted as an inner nativeasyncarrow function, invoked immediately. The promise it returns is wrapped back into aTask, so the method's return value is aTaskon both the C# and the JavaScript side. - Each
awaitin the body is emitted as a nativeawaitover an adapter that accepts either aTaskor a native promise.
Task is not a native promise
This is the main thing to know when you cross into JavaScript. The runtime's
Task is its own type — it exposes continueWith, isFaulted,
getAwaitedResult — and it is not thenable. C# await works on it because
the compiler routes every awaited expression through an adapter that converts a
Task (or a getAwaiter()-shaped object, or an already-native promise) into a
promise the engine can await.
Practically:
| From | To | What you do |
|---|---|---|
C# await over a C# Task |
— | Nothing; just await. |
C# await over a JS promise |
— | Type the extern as Task/Task<T> and await it. |
JS consuming a C# Task |
promise | Wrap it: Transpose.toPromise(task).then(…). |
Interoperating with JavaScript promises
Consuming a promise
Declare the external function as returning Task or Task<T> and await it. The
adapter recognises a native promise and awaits it directly, with no conversion
step of your own.
[External]
public static class MyJsLib
{
public static extern Task<int> LongRunningOperation();
}
// Usage
int result = await MyJsLib.LongRunningOperation();
A rejected promise becomes a thrown exception, so an ordinary try/catch
around the await works.
Returning a task to JavaScript
A C# method that returns Task returns a runtime Task in JavaScript, so a
caller has to adapt it:
public static async Task<string> GetData()
{
await Task.Delay(1000);
return "Hello from C#";
}
// JavaScript — Task is not thenable, so adapt it first.
Transpose.toPromise(MyNamespace.MyClass.GetData()).then(result => {
console.log(result); // "Hello from C#"
});
Transpose.toPromise also accepts a value or a promise unchanged, so it is safe
to apply unconditionally.
If the boundary matters to you, an alternative is to hand JavaScript a plain
callback (Action<T>) and invoke it from C# when the work completes — no
adaptation needed on either side.
Task.Delay
Task.Delay is implemented with setTimeout.
await Task.Delay(1000); // resumes about a second later
It honours a CancellationToken, which is also implemented on timers.
Considerations
- There is one thread.
awaityields to the event loop; it does not move work to a background thread. A long synchronous computation blocks rendering and input regardless of how manyawaits surround it. Break such work into chunks separated byawait Task.Delay(0), or move it to a Web Worker through aTranspose.Corebinding. Task.Rundoes not offload work. It schedules the delegate on the event loop and wraps the result in aTask. The delegate still runs on the main thread; the only thing you gain is that it runs later.System.Threading.Threadhas no browser equivalent and is not supported. Neither are the blocking waits (Task.Wait(),.Result,Task.WaitAll) — there is no thread to block, soawaitis the only option.- Fire-and-forget
async voidworks, but an exception thrown in it has nowhere to surface. Preferasync Taskand await it, or catch inside.