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) |
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
Notes#
- Only
TaskandTask<T>are supported (ValueTaskis not supported) - Pending tasks are resolved during the next
Tick()call (up to 50 per frame to avoid stalling) - 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