Skip to content

Promise System

ffredyk edited this page Jul 23, 2026 · 1 revision

Promise System

Every ScriptHandle in SQ# is a promise — an async operation that will complete with a value. Built on .NET Task<SqValue> under the hood.

ScriptHandle = Promise

When you spawn code, you get a ScriptHandle:

_handle = spawn { sleep 2; return 42; };
// _handle is a promise that will resolve with 42

The handle tracks:

  • Status: running or resolved
  • Result: the return value (nil if none)
  • Error: if the fiber threw an uncaught exception

Basic Operations

Check Status

scriptDone _handle;            // true if resolved
isNull _handle;                // same (SQF compat)

Wait for Result

_result = await _handle;       // suspend until resolved, return value

Cancel

terminate _handle;             // force-resolve immediately

Timeout

_race = _handle timeout 5;     // race against 5 seconds
_result = await _race;
if (isNil "_result") then {
    terminate _handle;         // timeout — cancel original
};

await in Detail

await suspends the current fiber until the handle resolves. Returns the handle's result value:

_handle = spawn { sleep 1; return "done"; };
_result = await _handle;       // "done"

// Already-resolved handles return immediately:
_handle2 = spawn { 1 + 2; };  // might complete instantly
_result2 = await _handle2;     // returns 3 immediately (no suspension)

Important: Only works in scheduled (spawned) code. Use canSuspend to guard.

timeout

Races a handle against a timer. Returns a new handle:

_handle = spawn { sleep 10; return "slow"; };
_race = _handle timeout 3;

_result = await _race;
// If original completes within 3s → _result = "slow"
// If 3s passes first → _result = nil (original still running)

The original handle is not automatically terminated. Use terminate if needed:

if (isNil "_result") then { terminate _handle; };

terminate

Force-resolves a handle. The associated fiber stops immediately — no cleanup runs:

_handle = spawn { infiniteLoop(); };
terminate _handle;             // stops the fiber

// Already-resolved handles are unaffected:
terminate _handle;             // no-op

Promise Combinators

Advanced patterns for coordinating multiple async operations.

PromiseAll — Wait for All

_handles = [
    spawn { sleep 1; return "a"; },
    spawn { sleep 2; return "b"; },
    spawn { sleep 3; return "c"; }
];

_results = PromiseAll _handles;  // ["a", "b", "c"]
// Resolves when ALL handles complete

PromiseRace — First Wins

_handles = [
    spawn { sleep 2; return "slow"; },
    spawn { sleep 0.5; return "fast"; }
];

_winner = PromiseRace _handles;  // "fast"
// Resolves with the FIRST handle to complete

PromiseAny — First Non-Error

_handles = [
    spawn { sleep 1; throw "fail"; },
    spawn { sleep 2; return "ok"; }
];

_result = PromiseAny _handles;   // "ok"
// Resolves with the first handle that succeeds (doesn't throw)

Error Handling

Errors in Spawned Code

_handle = spawn {
    throw "Something broke";
};

_result = await _handle;       // nil (error — no result)

// Error propagates to await-er
try {
    _result = await _handle;
} catch {
    print f"Async error: {_exception}";
};

Defensive Await

_handle = spawn { riskyOperation(); };
try {
    _result = await _handle;
} catch {
    _result = "fallback";
};

Progress Reporting

_handle = spawn {
    for "_i" from 0 to 100 step 10 do {
        _thisScript setProgress _i;
        sleep 0.5;
    };
    return "complete";
};

// Check progress (non-suspending)
_progress = _handle getProgress;  // 0-100

Common Patterns

Sequential Async Operations

_data = await (spawn { fetchData(); });
_processed = await (spawn { process(_data); });
_saved = await (spawn { save(_processed); });

Parallel Work with Timeout

_handles = [];
for "_i" from 0 to 9 do {
    _handles pushBack (spawn { doWorkUnit(_i); });
};

_race = PromiseAll _handles timeout 30;
_results = await _race;
if (isNil "_results") then {
    { terminate _x } forEach _handles;
    print "Work timed out";
};

Fire-and-Forget

spawn { diag_log "background task"; };  // ignore handle

_thisScript

spawn {
    _myHandle = _thisScript;   // reference to own handle
    sleep 5;
    // _myHandle is now the handle of this spawn
};

vs SQF

Feature SQF SQ#
Threading Same scheduler only Cross-scheduler, cross-thread
C# interop Not possible await handle.ToTask()
Cancellation terminate only terminate + CancellationToken
Error handling Silent fail Exceptions propagate
Combinators None PromiseAll, PromiseRace, PromiseAny
Timeout waitUntil [h, t] handle timeout seconds
Unscheduled await ❌ (must be scheduled)

See Also

SQ# Wiki

Home

Engine Docs

Migration

Commands

Value Constructors

Arithmetic

Comparison

Logic

Array

String

Math

Random

Type & Introspection

HashMap

Code Execution

Concurrency

Scheduler

Thread Safety

Error

Output

Time

Multiplayer

Compiler

Clone this wiki locally