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 dependency-free, standalone library.
Basic Usage: Start with a simple deferred computation:
use PhpStandardLibrary\Promise\Promise;
// Create a new promise
$promise = new Promise(function ($resolve, $reject) {
// Simulate async work (e.g., API call, DB query)
if (rand(0, 1)) {
$resolve("Success!");
} else {
$reject(new \RuntimeException("Failed!"));
}
});
// Attach callbacks
$promise->then(
function ($value) {
echo "Resolved: " . $value;
},
function (\Throwable $error) {
echo "Rejected: " . $error->getMessage();
}
);
First Use Case: Wrap a Laravel queue job or external API call to chain async operations:
use PhpStandardLibrary\Promise\Promise;
use Illuminate\Support\Facades\Http;
$apiPromise = new Promise(function ($resolve, $reject) {
$response = Http::get('https://api.example.com/data');
if ($response->successful()) {
$resolve($response->json());
} else {
$reject(new \Exception("API request failed"));
}
});
$apiPromise->then(
function ($data) {
// Process data or dispatch a follow-up job
dispatch(new ProcessDataJob($data));
}
);
Key Entry Points:
Promise::all(): Wait for multiple promises to resolve.Promise::race(): Resolve/reject as soon as one promise settles.Promise::reject()/Promise::resolve(): Create settled promises directly.Replace nested callbacks with .then() chains:
$promise1 = new Promise(...);
$promise2 = new Promise(...);
$promise1->then(
function ($result) {
return new Promise(function ($resolve) use ($result) {
// Async step 2
$resolve($result . "_processed");
});
}
)->then(function ($finalResult) {
// Final step
});
Promise::all()Run multiple async operations concurrently:
$promise1 = fetchUserData();
$promise2 = fetchOrderData();
$promise3 = fetchPaymentData();
Promise::all([$promise1, $promise2, $promise3])->then(function ($results) {
// $results = [userData, orderData, paymentData]
});
Promise::setUnhandledRejectionHandler(function (\Throwable $error) {
\Log::error("Unhandled rejection: " . $error->getMessage());
});
.catch() or the second argument in .then():
$promise->then(
function ($value) {},
function (\Throwable $error) {
\Log::error($error);
}
);
$jobPromise = new Promise(function ($resolve) {
dispatch(new LongRunningJob())->onQueue('promises');
$resolve("Job dispatched");
});
$responsePromise = new Promise(function ($resolve, $reject) {
$response = Http::get('...');
$resolve($response->json());
});
Promise::race([$promise1, $promise2])->then(...);
Promise::any([$promise1, $promise2])->then(...);
Extend Laravel’s Bus or Queue to return promises:
// app/Providers/AppServiceProvider.php
use PhpStandardLibrary\Promise\Promise;
use Illuminate\Support\Facades\Bus;
Bus::macro('promise', function ($command) {
return new Promise(function ($resolve, $reject) {
try {
$response = Bus::dispatch($command);
$resolve($response);
} catch (\Throwable $e) {
$reject($e);
}
});
});
Usage:
Bus::promise(new ProcessOrder())->then(...);
Create domain-specific combinators:
// app/Support/PromiseHelpers.php
use PhpStandardLibrary\Promise\Promise;
class PromiseHelpers {
public static function allSettled(array $promises) {
return Promise::all(array_map(function ($promise) {
return $promise->then(
fn($value) => ['status' => 'fulfilled', 'value' => $value],
fn(\Throwable $error) => ['status' => 'rejected', 'reason' => $error]
);
}, $promises));
}
}
Use Mockery to stub promises in tests:
$mockPromise = Mockery::mock(Promise::class);
$mockPromise->shouldReceive('then')
->once()
->withAnyArgs()
->andReturnSelf();
$mockPromise->shouldReceive('resolve')->once()->with('test');
// In test
$promise = new Promise(...);
$promise->then(function ($value) {
$this->assertEquals('test', $value);
});
Unresolved Promises Leak Memory
.finally() or use a timeout:
$promise->finally(function () {
// Cleanup (e.g., close DB connections)
});
withTimeout() for queue jobs.Callback Hell in Disguise
.then() chains defeat the purpose of Promises.Promise::all() or extract logic into helper functions.Global State in Promises
Error Handling Gaps
setUnhandledRejectionHandler and log errors to Sentry/Telescope:
Promise::setUnhandledRejectionHandler(function (\Throwable $error) {
\Sentry\captureException($error);
});
Blocking the Event Loop (Indirectly)
defer():
$promise->then(function () {
\Illuminate\Support\Facades\Bus::defer(fn() => $heavyTask());
});
Log Promise States
Extend the Promise class to log resolutions/rejections:
class DebugPromise extends Promise {
public function __construct(callable $executor) {
parent::__construct(function ($resolve, $reject) use ($executor) {
\Log::debug("Promise created");
$executor(function ($value) use ($resolve) {
\Log::debug("Promise resolved: " . json_encode($value));
$resolve($value);
}, function (\Throwable $error) use ($reject) {
\Log::error("Promise rejected: " . $error->getMessage());
$reject($error);
});
});
}
}
Use Telescope for Monitoring Create a custom Telescope channel to track Promise activity:
// app/Providers/TelescopeServiceProvider.php
use Illuminate\Support\Facades\Telescope;
Telescope::macro('promise', function ($promise) {
$promise->then(
fn($value) => Telescope::log('Promise resolved', ['value' => $value]),
fn(\Throwable $error) => Telescope::log('Promise rejected', ['error' => $error->getMessage()])
);
});
Xdebug Async Support For local debugging, use Xdebug’s async stack traces:
xdebug.mode=debug
xdebug
How can I help you explore Laravel packages today?