internal/promise
Lightweight Promises/A implementation for PHP (fork of reactphp/promise). PHP 8.1+ compatible with strict types and improved type annotations. Drop-in replacement for react/promise v2/v3 with reusable rejection handling and safer defaults.
Installation:
composer require internal/promise
Ensure your project uses PHP 8.1+ (required for full compatibility).
First Use Case:
Replace a synchronous callback with a Promise for an async HTTP request (e.g., using Guzzle):
use Internal\Promise\Promise;
use GuzzleHttp\Client;
$client = new Client();
$promise = Promise\resolve()
->then(function () use ($client) {
return $client->requestAsync('GET', 'https://api.example.com/data');
})
->then(function ($response) {
return json_decode($response->getBody(), true);
});
$promise->then(function ($data) {
// Handle resolved data
})->catch(function (\Throwable $e) {
// Handle rejection
});
Key Entry Points:
Promise\resolve($value): Create a resolved promise.Promise\reject($reason): Create a rejected promise.Deferred: For manual promise control (e.g., async operations).Promise\all([$promise1, $promise2]): Run promises in parallel.Promise\race([$promise1, $promise2]): Return the first settled promise.Where to Look First:
then/catch/finally).when.js; details methods like cancel().composer test to see real-world usage patterns.Use Deferred to wrap Eloquent queries or raw PDO calls:
use Internal\Promise\Deferred;
use Illuminate\Support\Facades\DB;
$deferred = new Deferred();
DB::connection()->getPdo()->queryAsync('SELECT * FROM users', function ($result) use ($deferred) {
$deferred->resolve($result->fetchAll());
});
$promise = $deferred->promise();
$promise->then(function ($users) {
// Process users
});
Combine Promise\all() with Laravel HTTP clients:
use Illuminate\Support\Facades\Http;
use Internal\Promise\Promise;
$promises = [
Promise\resolve()->then(fn() => Http::get('https://api1.example.com/data')),
Promise\resolve()->then(fn() => Http::get('https://api2.example.com/data')),
];
Promise\all($promises)->then(function ($responses) {
// Merge responses
});
Promise\set_rejection_handler(function (\Throwable $e) {
\Log::error('Unhandled promise rejection', ['exception' => $e]);
});
catch():
$promise->catch(function (\Throwable $e) {
\Toast::error("Failed to load data: " . $e->getMessage());
});
Cancel long-running promises (e.g., timeouts):
$promise = Promise\resolve()->then(fn() => sleep(5));
$promise->cancel(); // Stops execution if supported by the underlying operation.
Integrate with Laravel’s Bus or Queue:
use Illuminate\Bus\Queueable;
use Internal\Promise\Promise;
class ProcessOrder implements Queueable
{
public function handle()
{
return Promise\resolve()
->then(fn() => $this->validateOrder())
->then(fn() => $this->shipOrder())
->finally(fn() => \Log::info('Order processed'));
}
}
->wait() in synchronous contexts (e.g., route handlers). Use then() for async continuation.resolve(T $value):
$promise = Promise\resolve<string>('data');
Deferred or Promise to the container for dependency injection:
$this->app->bind(Deferred::class, fn() => new Deferred());
No Implicit await:
Unlike JavaScript, PHP lacks await syntax. Use then() chains or libraries like Amp for coroutines.
// ❌ Avoid this in PHP (pseudo-code):
// $data = await $promise;
// ✅ Correct:
$promise->then(fn($data) => /* ... */);
Rejection Handling:
error_log(). Override with set_rejection_handler().catch() to avoid silent promise failures.PHP 8.1+ Strict Types:
resolve() Requires a Value: Use null for no value:
Promise\resolve(null); // Valid
Promise\resolve(); // ❌ TypeError
reject() Requires Throwable: Never pass strings or objects:
Promise\reject(new \RuntimeException('Error')); // ✅
Promise\reject('Error'); // ❌ TypeError
Cancellation Limitations:
sleep()). Use timeouts or manual checks:
$promise = Promise\resolve()->then(fn() => sleep(10));
$promise->cancel(); // May not work; use a timeout instead.
Global State:
Promise\set_rejection_handler(fn() => /* ... */);
Inspect Promises:
Use Promise\is_pending(), is_rejected(), or is_fulfilled() to debug state:
if (Promise\is_pending($promise)) {
\Log::debug('Promise still pending');
}
Stack Traces:
Enable E_ALL and check error_log() for unhandled rejections. For custom logging:
Promise\set_rejection_handler(function (\Throwable $e) {
\Sentry\captureException($e);
});
Memory Leaks:
finally() for cleanup:
$promise->finally(fn() => $resource->close());
Custom Promise Classes:
Extend PromiseInterface for domain-specific logic (though classes are final, use composition):
class ApiPromise implements PromiseInterface
{
private PromiseInterface $promise;
public function __construct(PromiseInterface $promise)
{
$this->promise = $promise;
}
public function then(callable $onFulfilled): PromiseInterface
{
return $this->promise->then($onFulfilled);
}
// Implement other PromiseInterface methods...
}
Async Laravel Helpers: Create a facade for common patterns:
use Illuminate\Support\Facades\Facade;
class PromiseFacade extends Facade
{
protected static function getFacadeAccessor() { return 'promise'; }
}
Register in AppServiceProvider:
$this->app->singleton('promise', fn() => new PromiseHelper());
Testing:
Use Promise\resolve()/reject() in tests to mock async behavior:
$this->expectException(\RuntimeException::class);
Promise\reject(new \RuntimeException('Test error'))->wait();
Avoid wait():
Blocking calls (e.g., in tests) can hang the event loop. Use then() or Promise\all() instead.
Reuse Deferreds:
Reuse Deferred instances for multiple operations to reduce overhead:
$deferred = new Deferred();
$promise1 = $deferred->promise();
$promise2 = $deferred->promise(); // Shares the same resolution path.
Parallelism: Use `Promise
How can I help you explore Laravel packages today?