php-standard-library/promise
Lightweight Promise implementation for PHP with a simple, standards-inspired API. Create, resolve, reject, and chain async-style operations, handle errors, and compose results with familiar then/catch patterns—ideal for libraries that need non-blocking workflows.
Install the Package:
composer require php-standard-library/promise
No additional configuration is required—it’s a lightweight, dependency-free library.
Basic Usage: Start with a simple deferred computation:
use PhpStandardLibrary\Promise\Promise;
$promise = new Promise(function ($resolve, $reject) {
// Simulate async operation (e.g., API call, DB query)
if ($someCondition) {
$resolve("Success!");
} else {
$reject(new \Exception("Failed!"));
}
});
// Attach callbacks
$promise->then(function ($result) {
echo "Resolved: " . $result;
})->catch(function (\Throwable $e) {
echo "Rejected: " . $e->getMessage();
});
First Use Case: Wrap a Laravel queue job or HTTP request to chain async operations:
use Illuminate\Support\Facades\Http;
use PhpStandardLibrary\Promise\Promise;
$promise = new Promise(function ($resolve) {
$response = Http::get('https://api.example.com/data');
$resolve($response->json());
});
$promise->then(function ($data) {
// Process data synchronously
return User::createFromApiData($data);
})->then(function ($user) {
echo "User created: " . $user->name;
});
Where to Look First:
Promise section).Promise.php for implementation details.tests/ directory in the repo for usage patterns.Use .then() to chain dependent async tasks (e.g., API calls, DB operations):
$promise = new Promise(function ($resolve) {
$resolve(fetchUserData());
})->then(function ($user) {
return fetchUserOrders($user->id); // Returns a new Promise
})->then(function ($orders) {
return processOrders($orders);
})->catch(function (\Throwable $e) {
Log::error("Async chain failed: " . $e->getMessage());
});
Promise::all()Run multiple async operations in parallel and wait for all to complete:
use PhpStandardLibrary\Promise\Promise;
$promise1 = new Promise(function ($resolve) {
$resolve(fetchData('users'));
});
$promise2 = new Promise(function ($resolve) {
$resolve(fetchData('orders'));
});
Promise::all([$promise1, $promise2])->then(function ($results) {
$users = $results[0];
$orders = $results[1];
// Combine results
});
Promise::race()Resolve as soon as one of the promises settles (useful for timeouts):
$promise1 = new Promise(function ($resolve) {
sleep(2);
$resolve("Slow result");
});
$promise2 = new Promise(function ($resolve) {
$resolve("Fast result");
});
Promise::race([$promise1, $promise2])->then(function ($result) {
echo "First to resolve: " . $result; // "Fast result"
});
$jobPromise = new Promise(function ($resolve) {
$job = new ProcessOrdersJob($orderId);
dispatch($job)->onQueue('promises');
$resolve($job); // Resolve when job completes (requires event/listener)
});
$httpPromise = new Promise(function ($resolve) use ($client) {
$response = $client->get('/api/data');
$resolve($response->json());
});
Promise::setUnhandledRejectionHandler(function (\Throwable $e) {
Log::critical("Unhandled Promise rejection: " . $e->getMessage());
// Optionally notify monitoring tools (e.g., Sentry)
});
function retryPromise($promise, $maxRetries = 3) {
return $promise->catch(function (\Throwable $e) use ($promise, $maxRetries) {
if ($maxRetries <= 0) throw $e;
return retryPromise($promise, $maxRetries - 1);
});
}
Service Providers:
Bind the Promise class to the container for easy access:
// app/Providers/AppServiceProvider.php
public function register() {
$this->app->singleton('promise', function () {
return new \PhpStandardLibrary\Promise\Promise();
});
}
Use via app('promise') or create a facade.
Macros for Laravel:
Extend Laravel’s Bus or Queue to return Promises:
// app/Helpers/PromiseHelper.php
if (!function_exists('promiseDispatch')) {
function promiseDispatch($job, $queue = null) {
return new \PhpStandardLibrary\Promise\Promise(function ($resolve) use ($job, $queue) {
dispatch($job)->onQueue($queue)->afterResolved(function () use ($resolve) {
$resolve(true);
});
});
}
}
Testing Promises:
Use Mockery to stub Promise resolutions:
$mockPromise = Mockery::mock(\PhpStandardLibrary\Promise\Promise::class);
$mockPromise->shouldReceive('then')->once()->withAnyArgs();
$mockPromise->shouldReceive('catch')->never();
Async Event Listeners: Convert sync event listeners to async with Promises:
Event::listen(OrderPlaced::class, function () {
$promise = new Promise(function ($resolve) {
$resolve(notifyCustomer());
});
return $promise; // Return Promise for chaining
});
.then() synchronously in a loop (e.g., Promise::all(array_map(...)) can block).Promise::all() calls to avoid memory spikes.Callback Hell Revisited:
.then() chains can still resemble callback hell.Promise::all() or flatten chains with Promise::then() returning Promises.Unresolved Promises:
.finally() or use a TTL (e.g., finally(fn() => $promise->cancel())).Error Propagation:
.then() callbacks may not bubble up correctly if not wrapped in try/catch.Promise::catch() after every .then() or wrap callbacks in try/catch.PHP’s Async Limitations:
Global State:
Testing Complexity:
.then() and .catch() paths.Mockery or create a PromiseTestCase helper:
$promise = new Promise(function ($resolve) { $resolve("test"); });
$promise->then($this->expectsOnce()->with("test"));
Log Promise States:
$promise->then(function ($result) {
Log::debug("Promise resolved", ['result' => $result]);
})->catch(function (\Throwable $e) {
Log::error("Promise rejected", ['error' => $e->getMessage()]);
});
Xdebug for Async:
Xdebug with async stack traces to inspect Promise chains.How can I help you explore Laravel packages today?