Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Promise Laravel Package

windwalker/promise

Windwalker Promise is a PHP library for asynchronous-style control flow, offering promises, deferreds, and helpers to chain callbacks, handle resolution and rejection, and orchestrate non-blocking tasks more cleanly in PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

windwalker/promise is a small Promises/A+-style library for PHP: you get Promise, Deferred, and a handful of combinators to chain asynchronous work instead of nesting callbacks. It is a building block, not a framework — treat it like guzzlehttp/promises or react/promise but lighter.

Install and locate the API

composer require windwalker/promise

Everything lives under the Windwalker\Promise namespace. The two files you will read first are:

  • Windwalker\Promise\Promise — the interface plus static helpers (resolve, reject, all, race, settle).
  • Windwalker\Promise\Deferred — the producer side (promise(), resolve(), reject()).

Skim the package's test/ directory before anything else; for a low-traffic repository the tests are the most reliable API contract, especially around chaining and executor semantics.

First use case: wrap a callback-based operation

The idiomatic entry point is a Deferred. Take an existing callback API (a queue push, a socket write, a legacy HTTP client) and hand back a promise:

use Windwalker\Promise\Deferred;

function fetchAsync(string $url): \Windwalker\Promise\PromiseInterface
{
    $deferred = new Deferred();

    someLegacyClient($url, function ($err, $body) use ($deferred) {
        $err ? $deferred->reject($err) : $deferred->resolve($body);
    });

    return $deferred->promise();
}

Then consume it with ->then() and ->catch() on the caller side. That conversion step is 90% of what this package does day-to-day — most of your code will be adapters turning callback or event-driven APIs into promises, with the promise graph itself staying thin.

Implementation Patterns

Deferred as the producer/consumer boundary. Keep Deferred creation in adapter functions and never leak the deferred out — only return promise(). That gives you a single, disciplined place to call resolve/reject and makes double-settlement bugs easy to spot.

Chaining for sequential pipelines. then() returns a new promise, so read pipelines become flat:

$promise
    ->then(fn ($data) => $this->validate($data))
    ->then(fn ($data) => $this->repository->save($data))
    ->then(fn ($entity) => $this->dispatchEvent($entity))
    ->catch(fn ($e) => $this->logger->error($e->getMessage()))
    ->finally(fn () => $this->timer->stop());

Rule of thumb: if a then callback does more than one transformation, split it. Long callbacks break the error context that makes promise chains debuggable.

Error propagation via catch + throw. catch recovers a chain — return a value and downstream then continues. If you want to re-raise, throw inside catch; the resulting promise rejects with the new exception. Use this for wrapping lower-level exceptions into domain ones (DatabaseException, ValidationException) at the boundary of a service.

Combinators for fan-out. Promise::all($iterable) for "wait for everything, fail on first rejection," Promise::race() for timeout patterns, and Promise::settle() (if present in your version — verify in tests) when you need partial results. For per-item error isolation, map each item through catch before passing the array into all(); that way one bad item does not collapse the batch.

Integrate at the edge, not the core. Keep your domain services synchronous. Wrap them at the controller/job/console-command layer:

class ReportController
{
    public function show(int $id)
    {
        return $this->reportService->buildAsync($id)
            ->then(fn ($report) => new JsonResponse($report));
    }
}

This keeps tests simple and lets you drop the promise layer later if you move to fibers or Swoole.

Testing. Because promises run through a scheduler, use the library's wait/sync helper (commonly wait(), Promise::run() or an executor flush — check Promise:: static methods) rather than sleep() in tests. Resolve deferreds manually inside the test to verify both branches of every then/catch.

Queue jobs and CLI. A useful pattern: dispatch a promise chain in a console command with per-step logging, so failures show which stage of the chain rejected. Combine with finally to push metrics.

Gotchas and Tips

Rejections are silent by default. An unhandled rejected promise typically just sits there — no exception, no log. Any chain you create should end in catch() (even if only to log and rethrow), otherwise failures vanish. Add a global "unhandled" hook if the version supports one, otherwise lint for ->then( without a terminal catch.

Returning a promise from then flattens; returning a value does not. then(fn () => $otherPromise) waits for that promise. then(function () use ($p) { $p; }) returns null and does not wait — an easy mistake that makes chains fire out of order.

Synchronous resolution is not the same as a synchronous chain. Even resolve()-backed promises typically defer callbacks to a scheduler tick, so code after ->then() may run before the callback body. Do not rely on ordering with side effects; sequence via then, not via statement order.

Double-settling is a real bug. Calling resolve() twice (common when a callback fires on both success and error, or a loop re-enters) either throws or is silently ignored depending on config. Guard adapters with a $settled boolean or check the promise state before settling.

Exceptions inside then reject the chain, they don't bubble. Wrap in catch and inspect getPrevious() if you re-wrap — the original trace gets lost otherwise.

all() rejects on the first failure but other operations keep running. There is no cancellation. Long-running side effects started in sibling promises still complete; design for idempotency or track them separately.

No cancellation / timeouts out of the box. Timeouts are a race() between your promise and a rejecting timer promise. Remember to clear the timer in finally.

Config quirks. This package is small and rarely configured, but if a scheduler is injected, verify which one — a global vs. per-promise scheduler mismatch is the classic "callbacks never fire" bug. Check composer.json for optional deps (event loop adapters) before assuming async behavior.

Low-traffic package caution. With ~0 stars and no dependents, the API surface can drift between minor versions. Pin the version in composer.json and assert behavior in your own test suite rather than trusting upstream docs. When a Promise method you remember is missing, check the Promise interface first — static helpers are sometimes added as traits or standalone classes.

Extension point. PromiseInterface is the correct abstraction to type-hint and mock. Implement it for custom promise-like objects (e.g., a fiber-backed variant) if you need to migrate off this package later without touching call sites.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package