Async C# Methods#
C# async Task methods automatically become JavaScript Promises. You can await them just like any other async operation.
Basic Usage#
// C#
public static class DataLoader {
public static async Task<string> LoadDataAsync(string url) {
using var request = UnityWebRequest.Get(url);
await request.SendWebRequest();
return request.downloadHandler.text;
}
}// JavaScript
const data = await CS.DataLoader.LoadDataAsync("/api/data")
console.log(data)Non-generic Task methods work the same way:
// C#
public static async Task SaveAsync(string path, string content) {
await File.WriteAllTextAsync(path, content);
}// JavaScript
await CS.DataLoader.SaveAsync("save.json", JSON.stringify(state))
// resolves to null (no return value)Return Types#
| C# Return Type | JavaScript Result |
|---|---|
Task | null |
Task<int>, Task<float>, Task<bool> | number, number, boolean |
Task<string> | string |
Task<T> (reference type) | C# proxy object |
Already-completed Task<T> | Returns result directly (not a Promise) |
Already-completed tasks (e.g., Task.FromResult(42)) skip the Promise path entirely and return the result synchronously.
Error Handling#
Faulted and canceled tasks reject the Promise, so use standard try/catch:
// C#
public static class Api {
public static async Task<string> FetchUserAsync(int id) {
var user = await db.FindAsync(id);
if (user == null) throw new Exception("User not found");
return user.Name;
}
public static async Task<string> SlowQueryAsync(CancellationToken ct) {
await Task.Delay(5000, ct);
return "done";
}
}// JavaScript
try {
const name = await CS.Api.FetchUserAsync(999)
} catch (e) {
console.log(e.message) // "User not found"
}Canceled tasks reject with "Task was canceled":
try {
const result = await CS.Api.SlowQueryAsync(token)
} catch (e) {
console.log(e.message) // "Task was canceled"
}With React#
Use the standard useEffect + async pattern for loading data from C# async methods:
import { View, Label, render } from "onejs-react"
import { useState, useEffect } from "react"
function PlayerStats({ playerId }) {
const [stats, setStats] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
let cancelled = false
async function load() {
try {
const result = await CS.MyGame.StatsManager.GetStatsAsync(playerId)
if (!cancelled) setStats(result)
} catch (e) {
if (!cancelled) setError(e.message)
} finally {
if (!cancelled) setLoading(false)
}
}
load()
return () => { cancelled = true }
}, [playerId])
if (loading) return <Label text="Loading..." />
if (error) return <Label text={`Error: ${error}`} />
return (
<View>
<Label text={`Level: ${stats.Level}`} />
<Label text={`Score: ${stats.Score}`} />
</View>
)
}How It Works#
When you call a C# async method from JavaScript:
- If the task is already completed, the result is returned directly (no Promise)
- If the task is still pending, a Promise is created and linked to the task via an internal ID
- When the task completes on the C# side, the result is queued for delivery
- On the next frame tick, the Promise is resolved (or rejected) and your
awaitor.then()handler runs
This means async results arrive at least one frame after the task completes.
WebGL needs OneJS 3.2.1 or later. Earlier versions never settled a pending task in a web build: the promise neither resolved nor rejected, and nothing was logged. Everything built on a C# task was affected, including<Image src="https://...">,loadImageFromUrl,loadResourceAsync, and your own methods.fetchand WebSocket were fine, since WebGL uses the browser's own. If a web build hangs on anawaitthat works in the Editor, check your OneJS version first.
Notes#
- Only
TaskandTask<T>are supported (ValueTaskis not supported) - Up to 50 pending tasks settle per frame, so a large burst spreads over several frames
- If more than 100 tasks are pending, a warning is logged automatically
- For async asset loading from Unity's
Resources/folder, seeloadResourceAsync - See C# Interop for general C# usage from JavaScript