[IDEA] Native async/await syntax on top of Fibers

From: Date: Fri, 23 Oct 2026 20:37:36 +0000
Subject: [IDEA] Native async/await syntax on top of Fibers
Groups: php.internals 
Request: Send a blank email to internals+get-132907@lists.php.net to get a copy of this message
Hi internals, I'd like to gauge interest in adding native async/await syntax to PHP, built on top of the Fiber implementation we've had since 8.1. Fibers made it possible to write concurrent code without "function colouring", but in practice everyone still picks a userland library (Amp, ReactPHP, Revolt) together with its own Promise/Future type, and the code doesn't say directly what it means. The idea is to give the engine a small, standard vocabulary for this: async function fetch(string $name, float $secs): string { await Async\delay($secs); return "$name done"; } $a = fetch("a", 0.2); // starts right away, returns Async\Future $b = fetch("b", 0.2); echo await $a, await $b; // ~0.2s total, not 0.4s Proposed semantics, in short: - async can be applied to functions, methods, closures and arrow functions. Calling one runs its body in a new Fiber and returns an Async\Future. The task starts eagerly and runs until it awaits a Future that isn't complete yet. - await <expr> inside a task suspends that task. Outside a task (at the top level, or in a plain Fiber) it drives a small scheduler until the Future completes. It returns the value or rethrows the exception. - async is a contextual keyword: it is only a keyword when followed by function or fn, so existing userland async() functions keep working. await would become a reserved keyword, though it remains usable as a method, constant or property name. - Tasks still pending at shutdown are destroyed the same way suspended Fibers are, so their finally blocks run. Deadlocks throw an Error instead of hanging. - A minimal runtime API: final class Async\Future, Async\async(), Async\wait() (the function form of await), Async\delay() and Async\suspend(). I have a working prototype. Both constructs are desugared in the parser into calls to the runtime functions, so there are no new opcodes and no VM changes: https://github.com/php/php-src/pull/24623 It comes with tests and passes the existing test suite on a debug build (macOS arm64, NTS). I haven't tried it on Windows or ZTS yet. Questions I'd especially like feedback on: 1. Is reserving await an acceptable BC break? Unqualified functions named await() would break; namespaced calls such as Amp\Future\await() would not. 2. How should this relate to the True Async RFC? Should syntax like this be layered on top of that work rather than proposed separately? 3. What should I/O integration look like? The prototype only supports timers and cooperative yielding. Real-world usefulness depends on an event-loop or reactor story for streams and sockets. 4. Known rough edges in the desugaring approach: func_get_args() inside an async body returns [], stack traces show an extra {closure} frame, and reflection reports the return type as Async\Future. Are these acceptable, or is a dedicated engine-level implementation preferred? If there is interest, I'm happy to turn this into a proper RFC. Thanks, Abbas Al-Khateeb

« previous php.internals (#132907) next »