PHPackages                             hiblaphp/async - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. hiblaphp/async

ActiveLibrary

hiblaphp/async
==============

async/await implementation with structured concurrency.

01.1k↑77.4%PHPCI passing

Since Mar 20Pushed 2d agoCompare

[ Source](https://github.com/hiblaphp/async)[ Packagist](https://packagist.org/packages/hiblaphp/async)[ RSS](/packages/hiblaphp-async/feed)WikiDiscussions main Synced today

READMEChangelogDependenciesVersions (1)Used By (0)

Hibla Async
===========

[](#hibla-async)

**Context-independent async/await for PHP without function coloring.**

`hiblaphp/async` brings `async` and `await` to PHP as plain functions built on top of PHP 8.1 Fibers and the Hibla event loop. Unlike JavaScript, Python, or C#, `await()` works in both fiber and non-fiber contexts — you write normal functions and lift them into concurrency at the call site, not inside the function definition.

[![Latest Release](https://camo.githubusercontent.com/257346747415bc6b791924b60f91dff98d401a498c0089178963d14b206f4741/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f72656c656173652f6869626c617068702f6173796e632e7376673f7374796c653d666c61742d737175617265)](https://github.com/hiblaphp/async/releases)[![MIT License](https://camo.githubusercontent.com/942e017bf0672002dd32a857c95d66f28c5900ab541838c6c664442516309c8a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e7376673f7374796c653d666c61742d737175617265)](./LICENSE)

---

Contents
--------

[](#contents)

**Getting started**

- [Installation](#installation)
- [Introduction](#introduction)
- [The Function Coloring Problem](#the-function-coloring-problem)
- [Fibers and Coroutines](#fibers-and-coroutines)

**Core usage**

- [`async()` — Running Code Concurrently](#async--running-code-concurrently)
    - [One fiber runs at a time — never block inside `async()`](#one-fiber-runs-at-a-time--never-block-inside-async)
    - [Exceptions inside `async()`](#exceptions-inside-async)
    - [Avoid unnecessary wrapping](#avoid-unnecessary-wrapping)
- [`await()` — Suspending Until a Promise Settles](#await--suspending-until-a-promise-settles)
    - [Context-independent behavior](#context-independent-behavior)
    - [Rejection and cancellation](#rejection-and-cancellation)
    - [With `CancellationToken`](#with-cancellationtoken)
- [No Function Coloring in Practice](#no-function-coloring-in-practice)

**Features**

- [`asyncFn()` — Wrapping a Callable](#asyncfn--wrapping-a-callable)
- [`sleep()` — Async-Aware Pause](#sleep--async-aware-pause)
- [`inFiber()` — Context Detection](#infiber--context-detection)
- [Cancellation inside `async()`](#cancellation-inside-async)
    - [Automatic resource cleanup without `track()`](#automatic-resource-cleanup-without-track)
- [Combining with Promise Combinators](#combining-with-promise-combinators)

**Testing**

- [Testing Async Code](#testing-async-code)

**Reference**

- [Comparison with JavaScript async/await](#comparison-with-javascript-asyncawait)
- [API Reference](#api-reference)

**Meta**

- [Development](#development)
- [Credits](#credits)
- [License](#license)

---

Installation
------------

[](#installation)

```
composer require hiblaphp/async
```

**Requirements:**

- PHP 8.3+
- `hiblaphp/event-loop`
- `hiblaphp/promise`
- `hiblaphp/cancellation`

---

Introduction
------------

[](#introduction)

PHP has always been synchronous. When your code calls an HTTP endpoint, reads a file, or queries a database, it blocks and waits. One operation at a time, in sequence, from top to bottom. For short-lived scripts and simple request handlers this is fine. But the moment you need to fetch multiple things at once, handle WebSocket connections, or run background jobs without spinning up new processes, the model falls apart.

The standard solution in most languages is `async/await` — a way to mark functions as asynchronous and pause them at I/O boundaries while other work proceeds. But every major language that has implemented this — JavaScript, Python, C# — has introduced what is known as **function coloring**: `async`and `await` are syntax keywords that live inside the function definition. The moment a function uses `await`, it must be marked `async`, which changes its return type, which forces every caller to also be `async`. The color spreads upward through the entire call stack, creating two incompatible worlds — sync code and async code — that cannot be mixed freely.

`hiblaphp/async` solves this differently. `async()` and `await()` are plain PHP functions, not keywords. `await()` is context-independent — it checks whether it is running inside a Fiber at runtime and behaves accordingly. Inside a Fiber it suspends cooperatively. Outside a Fiber it falls back to blocking synchronously. A function that calls `await()` has no special marking, no changed return type, and no impact on its callers. The caller decides whether to give it concurrency by wrapping it in `async()` at the call site. The color lives at the call site, not inside the function.

This library is the top of the Hibla async stack. It sits on `hiblaphp/event-loop` for fiber scheduling, `hiblaphp/promise` for the promise model, and `hiblaphp/cancellation` for external cancellation coordination. Together these four libraries give you a complete async programming model for PHP that reads like synchronous code but runs cooperatively under the hood.

---

The Function Coloring Problem
-----------------------------

[](#the-function-coloring-problem)

In JavaScript, Python, and C#, `async` and `await` are keywords that live inside the function definition. The moment a function uses `await`, it must be marked `async`, which changes its return type, which forces every caller to also be `async`. The color spreads upward through the entire call stack:

```
// JavaScript — color spreads upward through every layer
async function getUser(id) {         // must be async
    return await fetchUser(id);      // uses await
}

async function buildPage(userId) {   // must be async because getUser is async
    const user = await getUser(userId);
    return user;
}

async function handleRequest(req) {  // must be async because buildPage is async
    const page = await buildPage(req.userId);
    return page;
}
```

Hibla solves this entirely. `await()` is just a regular PHP function that checks its execution context at runtime. A function that uses `await()` has no special marking, no changed return type, and no impact on its callers. The caller decides whether to give it concurrency by wrapping it in `async()`at the call site:

```
use function Hibla\async;
use function Hibla\await;

// A plain function — no special marking, no color
function getUser(int $id): User
{
    return await(fetchUser($id));
}

// Works synchronously at the top level — no async() needed
$user = getUser(1);

// Works concurrently when wrapped in async() — no changes to getUser()
$promise = async(fn() => getUser(1));
```

The color lives at the call site, not inside the function. This means you can write your entire application using normal functions with `await()` and introduce concurrency selectively where you need it.

---

Fibers and Coroutines
---------------------

[](#fibers-and-coroutines)

PHP Fibers were introduced in PHP 8.1 as a first-class stackful coroutine primitive. A **stackful coroutine** is a unit of execution that can be suspended and resumed at any point in its call stack — including inside deeply nested function calls. This is what separates Fibers from generators.

A generator can only suspend at the top-level `yield` inside the generator function itself. A Fiber can suspend from anywhere in its call stack. When a Fiber suspends, the entire call stack at that point — every function frame, every local variable, every instruction pointer — is frozen and preserved. When the Fiber is resumed, execution continues from exactly where it left off as if nothing happened.

A Fiber also has its own separate C-level stack, independent from the main thread stack, which is what makes suspension at any depth possible.

`async()` creates a Fiber and schedules it on the event loop. When the Fiber calls `await()` on a pending promise, it calls `Fiber::suspend()` internally, freezing the entire call stack and returning control to the event loop. When the promise resolves, `Loop::scheduleFiber()` queues the Fiber to be resumed, and the event loop restores the full call stack and continues execution from the suspension point:

```
function fetchUserProfile(int $id): PromiseInterface
{
    return async(function () use ($id) {
        $user   = await(Http::get("/users/$id"));
        $avatar = await(Http::get("/avatars/$id"));

        return ['user' => $user, 'avatar' => $avatar];
    });
}

async(function () {
    // Multiple profiles load concurrently because each async() call
    // runs in its own Fiber and suspends independently at each await()
    [$page1, $page2, $page3] = await(Promise::all([
        fetchUserProfile(1),
        fetchUserProfile(2),
        fetchUserProfile(3),
    ]));
});
```

---

`async()` — Running Code Concurrently
-------------------------------------

[](#async--running-code-concurrently)

`async()` wraps a callable in a PHP Fiber, schedules it on the event loop, and returns a `Promise` that resolves with the callable's return value. The callable does not run immediately — it is queued in the Fiber phase of the next event loop iteration:

```
use function Hibla\async;

$promise = async(function () {
    return 'hello from a fiber';
});

$promise->then(fn($value) => print($value)); // hello from a fiber
```

Multiple `async()` calls run concurrently. Each one gets its own Fiber and yields to others at every `await()` point:

```
$start = microtime(true);

async(function () {
    await(delay(1));
    echo "Task 1 done\n";
});

async(function () {
    await(delay(1));
    echo "Task 2 done\n";
});

async(function () {
    await(delay(1));
    echo "Task 3 done\n";
});

// All three run concurrently — total time ~1 second, not 3
Loop::run();
echo microtime(true) - $start; // ~1.0
```

---

### One fiber runs at a time — never block inside `async()`

[](#one-fiber-runs-at-a-time--never-block-inside-async)

The event loop runs only one Fiber at a time. Fibers are cooperatively scheduled — a Fiber runs until it explicitly suspends via `await()` or `sleep()`, at which point the event loop picks up the next ready Fiber.

A **blocking call** inside a Fiber — PHP's native `sleep()`, a synchronous database query, `file_get_contents()`, or any other call that blocks the OS thread — stalls the **entire event loop** for its duration. No other Fiber runs, no timers fire, no I/O is processed until the blocking call returns:

```
// Wrong — blocks the entire loop for 2 seconds
async(function () {
    \sleep(2); // PHP's native sleep — stalls everything
    echo "done\n";
});

// Correct — suspends this Fiber cooperatively, loop stays free
async(function () {
    sleep(2); // Hibla's sleep — use function Hibla\sleep
    echo "done\n";
});
```

Always use the async-aware equivalents from the Hibla ecosystem — `Http::get()` instead of `file_get_contents()`, `await(delay($n))` instead of `\sleep($n)`, stream watchers via `hiblaphp/stream` instead of blocking `fread()`. If you need to run genuinely blocking work or CPU-bound tasks, offload them to a separate process via `hiblaphp/parallel` rather than running them inside a Fiber.

---

### Exceptions inside `async()`

[](#exceptions-inside-async)

Any exception thrown inside an `async()` block rejects the returned promise. Always attach a `catch()` handler or `await()` the promise inside a `try/catch` when you care about errors:

```
$promise = async(function () {
    throw new \RuntimeException('Something went wrong');
});

$promise->catch(fn($e) => print($e->getMessage())); // Something went wrong
```

```
async(function () {
    try {
        $result = await(riskyOperation());
        return $result;
    } catch (\Throwable $e) {
        logError($e);
        return null;
    }
});
```

---

### Avoid unnecessary wrapping

[](#avoid-unnecessary-wrapping)

Each `async()` call creates a new PHP Fiber. Fibers are lightweight but not free — each one allocates a C-level stack and associated runtime state. Creating a Fiber just to immediately await a single promise that already exists adds overhead with no benefit.

If a function already returns a promise, `await()` it directly:

```
// Wrong — allocates a full Fiber just to await one existing promise
$result = await(async(fn() => await(Http::get('/api/data'))));

// Correct — await the promise directly, no Fiber needed
$result = await(Http::get('/api/data'));
```

The same applies to plain functions that use `await()` internally — they already work in both sync and async contexts without wrapping:

```
function getUserName(int $id): string
{
    $user = await(fetchUser($id));
    return $user->name;
}

// Wrong — getUserName() already works in both contexts
$name = await(async(fn() => getUserName(1)));

// Correct — call it directly
$name = getUserName(1);

// Only wrap in async() when you specifically want concurrent execution
$promise = async(fn() => getUserName(1)); // justified — explicit concurrency
```

Use `async()` when you genuinely need a Fiber — when you need to await multiple promises sequentially with logic in between, or when you want a block of code to run concurrently as its own unit of work:

```
// Good use — multiple awaits with logic between them
$promise = async(function () {
    $user    = await(fetchUser(1));
    $orders  = await(fetchOrders($user->id));
    $ratings = await(fetchRatings($user->id));

    return processData($user, $orders, $ratings);
});
```

---

`await()` — Suspending Until a Promise Settles
----------------------------------------------

[](#await--suspending-until-a-promise-settles)

`await()` suspends the current Fiber until the given promise settles, then returns the resolved value or throws the rejection reason:

```
use function Hibla\await;

$user = await(fetchUser(1));
echo $user->name;
```

---

### Context-independent behavior

[](#context-independent-behavior)

`await()` checks `Fiber::getCurrent()` at runtime and behaves accordingly:

- **Inside a Fiber** (`async()` block): suspends the Fiber cooperatively. The event loop continues running — other fibers, timers, and I/O all proceed while this Fiber waits.
- **Outside a Fiber** (top level or sync function): falls back to `Promise::wait()` and drives the event loop synchronously until the promise settles.

```
// Outside a Fiber — blocks synchronously
$user = await(fetchUser(1));

// Inside a Fiber — suspends cooperatively
async(function () {
    $user = await(fetchUser(1)); // other work runs while waiting
    echo $user->name;
});
```

This context-independence is what eliminates function coloring. A function that calls `await()` works correctly regardless of where it is called from — it does not need to know or care whether it is inside a Fiber.

---

### Rejection and cancellation

[](#rejection-and-cancellation)

If the awaited promise rejects, `await()` throws the rejection reason:

```
async(function () {
    try {
        $user = await(fetchUser(999)); // rejects with NotFoundException
    } catch (\NotFoundException $e) {
        echo "User not found\n";
    }
});
```

If the promise is cancelled before or during the await, `await()` throws `CancelledException`:

```
async(function () use ($token) {
    try {
        $user = await(fetchUser(1), $token);
    } catch (\Hibla\Promise\Exceptions\CancelledException $e) {
        echo "Fetch was cancelled\n";
    }
});
```

---

### With `CancellationToken`

[](#with-cancellationtoken)

Pass a `CancellationToken` as the second argument to automatically track the promise against the token. If the token is cancelled while the Fiber is suspended, the promise is cancelled and `CancelledException` is thrown at the `await()` call site — no manual `token->track()` needed:

```
use Hibla\Cancellation\CancellationTokenSource;
use function Hibla\async;
use function Hibla\await;

$cts = new CancellationTokenSource(5.0); // 5 second timeout

async(function () use ($cts) {
    try {
        $user   = await(fetchUser(1), $cts->token);
        $orders = await(fetchOrders($user->id), $cts->token);

        return compact('user', 'orders');
    } catch (\Hibla\Promise\Exceptions\CancelledException $e) {
        echo "Operation timed out or was cancelled\n";
    }
});
```

---

No Function Coloring in Practice
--------------------------------

[](#no-function-coloring-in-practice)

The full power of the no-coloring design becomes clear when you write library code that uses `await()` internally. The same code works in every context without any changes:

```
// Plain functions using await() internally — no special marking
function getUser(int $id): User
{
    return await(Http::get("/users/$id")->then(
        fn($r) => User::fromArray(json_decode($r->getBody(), true))
    ));
}

function getUserWithOrders(int $id): array
{
    $user   = getUser($id);
    $orders = await(fetchOrders($user->id));

    return compact('user', 'orders');
}
```

These are plain functions. Callers can use them in any of these ways without any changes to the functions themselves:

```
// 1. Synchronous — blocks at each call
$data = getUserWithOrders(1);

// 2. Single async task — runs in a Fiber, non-blocking
$promise = async(fn() => getUserWithOrders(1));

// 3. Concurrent — multiple users fetched in parallel
$promises = array_map(
    fn($id) => async(fn() => getUserWithOrders($id)),
    [1, 2, 3, 4, 5]
);
await(Promise::all($promises));

// 4. With concurrency limiting
await(Promise::concurrent(
    array_map(
        fn($id) => fn() => async(fn() => getUserWithOrders($id)),
        range(1, 100)
    ),
    concurrency: 10
));
```

The functions never changed. The concurrency strategy is entirely decided by the caller.

---

`asyncFn()` — Wrapping a Callable
---------------------------------

[](#asyncfn--wrapping-a-callable)

`asyncFn()` wraps a callable so that every call to it automatically runs inside `async()` and returns a `Promise`. Useful when you want to convert an existing function into a reusable async factory without changing the original function.

The same performance considerations from the [avoid unnecessary wrapping](#avoid-unnecessary-wrapping)section apply — only use it when the wrapped function genuinely needs its own Fiber context for concurrent execution:

```
use function Hibla\asyncFn;

function processRecord(array $record): array
{
    $enriched  = await(enrichRecord($record));
    $validated = await(validateRecord($enriched));

    return $validated;
}

// Create an async version without changing processRecord()
$asyncProcess = asyncFn('processRecord');

// Primary use case: passing to Promise::map() or Promise::concurrent()
await(Promise::map($records, $asyncProcess, concurrency: 10));
```

---

`sleep()` — Async-Aware Pause
-----------------------------

[](#sleep--async-aware-pause)

The `sleep()` function from `hiblaphp/async` is an async-aware replacement for PHP's native `sleep()`. It accepts fractional seconds — `sleep(0.5)` for 500ms, `sleep(1.5)` for 1.5 seconds.

- **Inside a Fiber:** suspends the current Fiber non-blocking. The event loop continues — other fibers, timers, and I/O run while this Fiber waits.
- **Outside a Fiber:** blocks the entire script, identical to PHP's native `sleep()`.

```
use function Hibla\sleep;

async(function () {
    echo "Task 1 start\n";
    sleep(2);
    echo "Task 1 done\n";
});

async(function () {
    echo "Task 2 start\n";
    sleep(1);
    echo "Task 2 done\n"; // runs before Task 1
});

// Output:
// Task 1 start
// Task 2 start
// Task 2 done  (~1 second)
// Task 1 done  (~2 seconds)
// Total time: ~2 seconds, not 3
```

> **Important:** Always import `Hibla\sleep` explicitly. PHP's native `sleep()` and Hibla's `sleep()` have the same name — if you forget the import you will silently call PHP's native blocking `sleep()` instead, stalling the entire event loop with no error or warning:
>
> ```
> use function Hibla\sleep; // required — do not omit
>
> async(function () {
>     sleep(1);  // Hibla's sleep — correct
>     \sleep(1); // PHP's native sleep — stalls the entire loop
> });
> ```

---

`inFiber()` — Context Detection
-------------------------------

[](#infiber--context-detection)

`inFiber()` returns `true` if the current code is executing inside a PHP Fiber. Useful for writing code that needs to behave differently depending on whether it is in an async context:

```
use function Hibla\inFiber;

function getStatus(): string
{
    if (inFiber()) {
        return await(fetchStatusAsync());
    }

    return fetchStatusSync();
}
```

In most cases you will not need this — `await()` already handles both contexts automatically. `inFiber()` is primarily useful when you want to select between fundamentally different implementations rather than just different blocking behaviors.

---

Cancellation inside `async()`
-----------------------------

[](#cancellation-inside-async)

Pass a `CancellationToken` to `await()` calls inside `async()` blocks to support external cancellation of the entire workflow. When the token is cancelled, the current `await()` throws `CancelledException` and the Fiber unwinds naturally through any `catch` or `finally` blocks.

Use `finally` inside `async()` to guarantee cleanup runs whether the workflow completes normally, throws, or is cancelled:

```
use Hibla\Cancellation\CancellationTokenSource;
use function Hibla\async;
use function Hibla\await;

$cts = new CancellationTokenSource();

$workflow = async(function () use ($cts) {
    $connection = openConnection();

    try {
        $user   = await(fetchUser(1), $cts->token);
        $orders = await(fetchOrders($user->id), $cts->token);
        $report = await(generateReport($user, $orders), $cts->token);

        return $report;
    } catch (\Hibla\Promise\Exceptions\CancelledException $e) {
        echo "Workflow cancelled\n";
        return null;
    } finally {
        // Always runs — normal completion, exception, or cancellation
        $connection->close();
    }
});

// Cancel from anywhere — the next await() in the workflow throws
Loop::addTimer(2.0, fn() => $cts->cancel());

$result = await($workflow);
```

If the token is already cancelled before the first `await()` inside the Fiber runs, the first `await()` call throws `CancelledException` immediately without suspending.

---

### Automatic resource cleanup without `track()`

[](#automatic-resource-cleanup-without-track)

When you pass a token to `await()`, the promise is automatically tracked by the token — you do not need to call `token->track($promise)` manually. This is particularly useful when awaiting promises that already have `onCancel()`handlers registered internally, such as HTTP requests from `hiblaphp/http-client`. The token triggers the promise's own `onCancel()`cleanup without any extra wiring at the call site:

```
$cts = new CancellationTokenSource(5.0);

$workflow = async(function () use ($cts) {
    // Http::get() has an onCancel() handler that aborts the curl request.
    // Passing $cts->token to await() is enough — no track() needed.
    $response = await(Http::get('https://api.example.com/users'), $cts->token);
    $data     = await(Http::get('https://api.example.com/orders'), $cts->token);

    return compact('response', 'data');
});
```

Passing the token directly to `await()` is the preferred pattern inside `async()` blocks — it is more concise and keeps the cancellation wiring at the `await()` call site where the suspension happens.

---

Combining with Promise Combinators
----------------------------------

[](#combining-with-promise-combinators)

`async()` returns a standard `Promise` so it composes naturally with all of `hiblaphp/promise`'s collection and concurrency methods.

### Running tasks concurrently with `Promise::all()`

[](#running-tasks-concurrently-with-promiseall)

```
[$users, $products, $stats] = await(Promise::all([
    async(fn() => fetchUsers()),
    async(fn() => fetchProducts()),
    async(fn() => fetchStats()),
]));
```

### Concurrency limiting with `Promise::concurrent()`

[](#concurrency-limiting-with-promiseconcurrent)

```
$results = await(Promise::concurrent(
    array_map(
        fn($id) => fn() => async(function () use ($id) {
            $user   = await(fetchUser($id));
            $orders = await(fetchOrders($user->id));

            return compact('user', 'orders');
        }),
        range(1, 100)
    ),
    concurrency: 10
));
```

### Racing with `Promise::race()`

[](#racing-with-promiserace)

```
$fastest = await(Promise::race([
    async(fn() => fetchFromRegionA()),
    async(fn() => fetchFromRegionB()),
    async(fn() => fetchFromRegionC()),
]));
```

### Timeout with `Promise::timeout()`

[](#timeout-with-promisetimeout)

```
$cts = new CancellationTokenSource();

try {
    $result = await(Promise::timeout(
        async(function () use ($cts) {
            return await(slowOperation(), $cts->token);
        }),
        seconds: 5.0
    ));
} catch (\Hibla\Promise\Exceptions\TimeoutException $e) {
    echo "Operation timed out\n";
}
```

---

Testing Async Code
------------------

[](#testing-async-code)

Because `await()` falls back to blocking synchronously outside a Fiber, you can test async code directly without any special test runner setup, event loop runner, or test helpers. Just call `await()` at the test level and it drives the loop until the promise settles:

```
public function test_fetch_user(): void
{
    $user = await(fetchUser(1));

    $this->assertEquals('John', $user->name);
}

public function test_concurrent_fetch(): void
{
    [$user, $orders] = await(Promise::all([
        fetchUser(1),
        fetchOrders(1),
    ]));

    $this->assertNotEmpty($orders);
}

public function test_cancellation(): void
{
    $cts = new CancellationTokenSource();
    $cts->cancel();

    $this->expectException(\Hibla\Promise\Exceptions\CancelledException::class);

    await(fetchUser(1), $cts->token);
}
```

This is one of the strongest practical advantages of context-independent `await()` — the same code that runs non-blocking in production runs blocking in tests, with no adaptation required.

---

Comparison with JavaScript async/await
--------------------------------------

[](#comparison-with-javascript-asyncawait)

JavaScriptHibla`await` usable in sync functionsNo — syntax errorYes — falls back to blockingFunction coloringYes — spreads upwardNo — color lives at call siteMarking a function asyncRequired (`async function`)Not requiredReturn type changeYes — always returns `Promise`No — return type unchangedConcurrency primitive`async function``async(fn() => ...)` at call siteAlready-settled promiseReturns on next microtaskReturns immediately, no suspensionContext detectionNot available`inFiber()`Testing async codeRequires async test runnerPlain `await()` — no setup neededThe fundamental difference is that JavaScript's `await` is a compile-time grammar rule — the parser enforces it at the syntax level. Hibla's `await()`is a runtime function call that checks `Fiber::getCurrent()`. This single difference is what eliminates function coloring entirely.

---

API Reference
-------------

[](#api-reference)

FunctionDescription`async(callable $function): PromiseInterface`Wrap a callable in a Fiber and schedule it on the event loop. Returns a Promise that resolves with the callable's return value. The callable does not run immediately — it is queued in the next Fiber phase.`await(PromiseInterface $promise, ?CancellationToken $token): mixed`Suspend the current Fiber until the promise settles (inside Fiber), or block synchronously (outside Fiber). Returns immediately without suspending for already-settled promises. Automatically tracks the promise on the token if provided. Throws on rejection or cancellation.`asyncFn(callable $function): callable`Wrap a callable so every call runs inside `async()` and returns a Promise. Creates a new Fiber per call.`sleep(float $seconds): void`Suspend the current Fiber non-blocking (inside Fiber), or block synchronously (outside Fiber). Accepts fractional seconds. Always import explicitly — PHP's native `sleep()` has the same name.`inFiber(): bool`Returns true if currently executing inside a PHP Fiber.---

Development
-----------

[](#development)

```
git clone https://github.com/hiblaphp/async.git
cd async
composer install
```

```
./vendor/bin/pest
```

```
./vendor/bin/phpstan analyse
```

---

Credits
-------

[](#credits)

- **API Design:** Inspired by JavaScript's `async`/`await` syntax and the function coloring problem described by Bob Nystrom in ["What Color is Your Function?"](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/). Hibla's context-independent `await()` is a direct solution to the problem that article described.
- **Fiber Scheduling:** Powered by [hiblaphp/event-loop](https://github.com/hiblaphp/event-loop).
- **Promise Integration:** Built on [hiblaphp/promise](https://github.com/hiblaphp/promise).
- **Cancellation:** Powered by [hiblaphp/cancellation](https://github.com/hiblaphp/cancellation).

---

License
-------

[](#license)

MIT License. See [LICENSE](./LICENSE) for more information.

###  Health Score

26

↑

LowBetter than 46% of packages

Maintenance65

Regular maintenance activity

Popularity20

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/adefeba01f7aab9f1eed1cc3f5015be296d86126a4adb50257d81ec0bf23c8ca?d=identicon)[RCalicdan](/maintainers/RCalicdan)

---

Top Contributors

[![rcalicdan](https://avatars.githubusercontent.com/u/163510169?v=4)](https://github.com/rcalicdan "rcalicdan (74 commits)")

### Embed Badge

![Health badge](/badges/hiblaphp-async/health.svg)

```
[![Health](https://phpackages.com/badges/hiblaphp-async/health.svg)](https://phpackages.com/packages/hiblaphp-async)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
