php-standard-library/async
Fiber-based async primitives for PHP: structured concurrency with cooperative multitasking. Run tasks concurrently, manage lifecycles, cancellations, and scopes predictably. Part of PHP Standard Library; docs and guides at php-standard-library.dev.
Installation
composer require php-standard-library/async
No additional configuration is needed beyond autoloading.
First Use Case: Async Task Execution in Laravel Replace a synchronous loop with parallel async tasks in a Laravel job or controller:
use Async\Task;
use Async\Promise;
public function processMultipleUsers(array $userIds) {
$tasks = collect($userIds)->map(fn($id) =>
Task::run(fn() => $this->processUser($id))
);
return Promise::all($tasks)->await();
}
private function processUser(int $id) {
// Simulate I/O-bound work (e.g., API call, DB query)
return User::find($id)->load('orders');
}
Where to Look First
Task class: The entry point for wrapping synchronous code as async tasks.
Task::run(), Task::later() (for delayed execution).Promise class: For composing and awaiting results.
Promise::all(), Promise::race(), Promise::then(), Promise::catch().Async facade: Laravel-friendly wrapper (if provided in future updates).
Async::task(fn() => ...)->await().Replace nested loops or sequential calls with parallel tasks:
// Sequential (blocking)
$user1 = $this->fetchUser(1);
$user2 = $this->fetchUser(2);
$user3 = $this->fetchUser(3);
// Parallel (non-blocking)
$promises = [
Task::run(fn() => $this->fetchUser(1)),
Task::run(fn() => $this->fetchUser(2)),
Task::run(fn() => $this->fetchUser(3)),
];
$users = Promise::all($promises)->await();
Chain async operations like middleware:
$result = Task::run(fn() => $this->step1())
->then(fn($data) => $this->step2($data))
->then(fn($data) => $this->step3($data))
->await();
Use catch() to handle failures gracefully:
Task::run(fn() => $this->fetchData())
->catch(fn(Throwable $e) => logger()->error("Fetch failed: {$e->getMessage()}"))
->await();
Offload async tasks to queues for true background processing:
// Async task dispatched to queue
Task::later(now()->addSeconds(10), fn() => $this->sendEmail($user))
->dispatch(); // Uses Laravel's queue system
Mix async and sync code in controllers/jobs:
public function handle(Request $request) {
// Sync: Validate request
$data = $request->validate([...]);
// Async: Process data in background
Task::run(fn() => $this->processData($data))
->detach(); // Fire-and-forget
return response()->json(['status' => 'processing']);
}
Use for non-blocking request processing (Laravel 11+ async routes):
public function handle(Request $request, Closure $next) {
return Async::run(fn() => $next($request))->await();
}
Warning: Avoid direct async DB queries in Laravel (connection leaks). Instead, use queues or batch processing:
// Anti-pattern (risky)
$users = Task::run(fn() => User::all())->await();
// Recommended
Task::run(fn() => User::chunk(100, fn($users) => $this->processChunk($users)))
->await();
Dispatch async event listeners:
event(new UserRegistered($user))
->then(fn() => Task::run(fn() => $this->sendWelcomeEmail($user)))
->catch(fn($e) => logger()->error($e));
sleep(), sync DB calls).Task::later() for delays or offload to queues.
// Bad: Blocks the event loop
Task::run(fn() => sleep(5));
// Good: Non-blocking delay
Task::later(now()->addSeconds(5), fn() => $this->doWork());
catch() or wrap in a try-catch:
Task::run(fn() => $this->riskyOperation())
->catch(fn($e) => report($e));
Task objects consume memory until garbage collected.await() or detach() tasks:
// Leak: Task runs but result is ignored
Task::run(fn() => $this->doWork());
// Fixed: Explicitly await or detach
Task::run(fn() => $this->doWork())->await();
// OR
Task::run(fn() => $this->doWork())->detach();
// Laravel 11+ async route
Route::get('/async', fn() => Async::run(fn() => $this->handleRequest()));
// Risky: Async DB query
Task::run(fn() => DB::table('users')->get());
// Safer: Queue job
ProcessUsersJob::dispatch();
Use Async::debug() (if available) or manually log task IDs:
$task = Task::run(fn() => $this->doWork());
logger()->debug("Task ID: {$task->getId()}");
Mock Task and Promise in PHPUnit:
$mockTask = Mockery::mock(Task::class);
$mockTask->shouldReceive('await')->andReturn($expectedResult);
$this->app->instance(Task::class, $mockTask);
Add timeouts to prevent hanging:
Task::run(fn() => $this->slowOperation())
->timeout(10) // 10 seconds
->catch(TimeoutException::class, fn() => logger()->warning("Task timed out"));
Extend Task to support cron-like scheduling:
class ScheduledTask extends Task {
public static function cron(string $expression, callable $callback) {
// Integrate with Laravel's scheduler or a custom cron parser
}
}
Implement exponential backoff for resilient tasks:
function retryAsync(callable $task, int $maxAttempts = 3) {
return Task::run(fn() => $task())
->catch(fn($e) => $maxAttempts > 0
? retryAsync($task, $maxAttempts - 1)
: throw $e);
}
Bind the package to Laravel’s container:
public function register() {
$this->app->singleton(Async::class, fn() => new Async());
$this->app->alias(Async::class, 'async');
}
Create a Laravel job that wraps async tasks:
class AsyncJob implements ShouldQueue {
use Dispatchable, InteractsWithQueue;
public function handle() {
Task::run(fn() => $this->asyncLogic())->await();
}
}
Set global concurrency limits to avoid overwhelming the system:
How can I help you explore Laravel packages today?