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

Promises Laravel Package

guzzlehttp/promises

Guzzle Promises is a small PHP promise library that powers Guzzle's asynchronous requests. It provides promise chaining, synchronous waiting, cancellation, and helpers for coordinating groups of promises. Install it directly when you need promise composition without the full HTTP client.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. This is not really a "should we adopt this" decision in a Laravel codebase — it is almost certainly already installed. guzzlehttp/guzzle depends on guzzlehttp/promises, and Laravel's own Illuminate\Http\Client is a thin wrapper over Guzzle. The real decision is narrower and more consequential: do we let GuzzleHttp\Promise\PromiseInterface into our application code, or do we keep it quarantined behind the HTTP client? That is an architectural boundary question, and it should be answered once, centrally, not per feature team.

Fit is strongest where the app is I/O-bound on third-party APIs and the alternative is a chain of sequential Http::get() calls. Fit is weakest where a team assumes "async" means parallelism — PHP here is cooperative, single-threaded, and nothing runs until something calls wait() or drains the task queue. Guzzle's cURL multi-handler provides the actual overlap; this library only provides the composition vocabulary around it. Any design that implies background execution after the response is sent is a misread.

Integration feasibility. Very high, and 3.1.0 does not change that. The release is a minor, non-breaking rename-plus-deprecation: it adds a State class for promise-state checks and deprecates Is, because PHP 8.6 deprecates is as a class name. No signature changes to then/wait/cancel/getState, no changes to Utils::*, no new requirements, no service provider, no config, no migration. composer require guzzlehttp/promises:^3.1 remains effectively a no-op where the transitive constraint already resolves.

What actually changed in 3.1.0 — and why it still matters:

  • This is a deprecation, not a fix. State::fulfilled() and Is::fulfilled() are the same predicate over the same getState() value. The long-documented trap (a promise fulfilled with an inner promise reports fulfilled while wait() can still throw) is unaffected by the rename. Teams that adopt State believing it is the "safe" API will reproduce the exact same defect. Our lint rule must target the pattern, not the class name.
  • It creates a mechanical cleanup item. Any 3.x code, test assertion, or phpunit helper calling Is::pending()/fulfilled()/rejected()/settled()/cancelled() will now emit deprecation notices under PHP 8.6 tooling. That is low-severity but it is dated work: removal is expected at the next major, so the migration window is "this cycle," not "eventually."
  • It confirms the forward-compatibility posture. A vendor shipping a deprecation shim before the PHP release that forces it is a good signal — it means the PHP 8.6 upgrade path is being de-risked for us. That marginally strengthens the maintainer-health story below.
  • Zero impact on the 2.x question. If any service is still on the 2.5.x line, this release is not the work item — the 2.5 → 3.0 major remains the dominant migration, and it is already scoped in the Integration Approach.

Technical risk. Still concentrated in semantics, not in the install:

  • wait() is a blocking call. In FPM it burns a worker for the duration of the slowest call in the group. In Octane/Swoole it can stall other requests on the same worker. Without the cURL multi-handler actually multiplexing, Utils::all() is at best "one round trip's worth" and at worst a slower sequential loop with extra objects.
  • State semantics remain the top trap — now with a second spelling. getState()/State::* describes how a promise was settled, not the eventual outcome. Branching on either is a defect pattern we should lint against in review.
  • Cancellation is cooperative and does not reach the socket. cancel() notifies handlers; it does not abort the in-flight cURL transfer or the downstream vendor's work. Teams will assume it saves cost/latency; it does not.
  • Promises are not serializable. 3.0 explicitly rejects native serialization of in-flight runtime objects, so a promise can never cross a queue boundary or a cache. Any "defer this to Horizon" design must hand off parameters, not promises.
  • Deprecation noise as a signal-hygiene risk. Teams that silence deprecations to clean up CI will also silence the next real warning. Fix Is usage rather than filtering it.

Key questions for the team:

  1. Which specific call sites are we converting, and what is the measured sequential latency we expect to remove? (No number, no project.)
  2. Will these run in FPM, Octane, or a queue worker — and does the chosen runtime tolerate a blocking wait() of N seconds?
  3. What is the per-request concurrency cap, and who owns it? Unbounded fan-out is a vendor rate-limit incident waiting to happen.
  4. Do we expose PromiseInterface in domain/service signatures, or wrap it (e.g. a ParallelRunner contract) so the library stays swappable?
  5. What is the timeout, retry, and partial-failure policy per integration — all (fail-fast) or settle/some (degrade gracefully)?
  6. New: how many GuzzleHttp\Promise\Is::* call sites exist across the monorepo, and do we migrate them to State::* inside this cycle's cleanup rather than deferring to the removal release?

Integration Approach

Stack fit. The natural seam is the service/adaptor layer that talks to third parties, not controllers and not Eloquent models. Laravel 10+ exposes Http::async(), which returns a PromiseInterface directly, so the idiomatic Laravel form is to collect pending requests in an adaptor, hand the array to a single application-owned runner, and resolve once at the boundary — keeping the promise type out of the rest of the codebase.

Layer Recommendation
Controllers / Livewire Never. Collect inputs, call one service method, return a response.
Service / adaptor Yes — the only place PromiseInterface appears.
Queued jobs Only if the job itself orchestrates the fan-out synchronously inside handle(). Never pass a promise in.
Shared packages Wrap it. Expose our own contract so the vendor type isn't in public signatures.

Migration path. Incremental and low-ceremony. The 2.x→3.x track is unchanged; 3.1.0 adds one small step.

  1. Audit first. composer why guzzlehttp/promises and composer show guzzlehttp/promises across the monorepo. Confirm which constraint is already resolved and whether anything is pinned to 1.x (EOL) or 2.x.
  2. Pin explicitly. Add a direct ^3.1 (or ^2.5 if PHP < 7.4 anywhere) requirement to composer.json so we are not silently riding someone else's transitive constraint. Lockfile review is the control point.
  3. Convert one integration. Pick a single fan-out site with ≥3 independent vendor calls and measurable latency. Ship behind a config flag if the call is revenue-affecting.
  4. Sweep the deprecation. grep -r "Promise\\\\Is\|Promise\\\\Is::" across app code, tests, and shared packages; replace with State::*. This is a mechanical, low-risk change and should ride along with step 2's lockfile bump rather than become its own initiative.
  5. Generalize. Once the pattern is proven, extract the runner and document it; do not let three teams invent three variants.

Compatibility. 3.0 requires PHP >= 7.4, < 8.7 and drops 7.2/7.3; it also marks previously non-final classes soft-final, makes static helpers non-instantiable, requires iterable inputs to collection helpers, and changes Utils::inspect() to return actual rejection reasons. 3.1.0 is additive and deprecation-only — no breaking change for 3.x consumers, but Is is now on a removal track, and PHP 8.6 deprecation reporting will flag it. If we are on 2.x anywhere, the 2.5 → 3.x jump still needs a code scan for: non-iterable inputs to all/some/any/Each::of, any reliance on Utils::inspect() returning the internal reason array, and any subclassing of the promise classes. Octane deserves a dedicated smoke test — long-lived workers change the blast radius of a leaked pending handler.

Sequencing.

Phase Scope Exit criteria
0 — Audit Dependency graph, PHP version matrix, candidate call sites, Is:: usage count Explicit version decision recorded
1 — Pilot One adaptor, one endpoint, feature-flagged p95 improvement measured against the sequential baseline
2 — Harden Timeouts, concurrency cap, partial-failure policy, cancellation semantics documented Failure-mode tests green
3 — Standardize Shared runner contract, lint rule against getState()/State::* branching, docs, Is→State sweep complete Two or more teams using the same pattern; zero Is:: references
4 — Steady state Dependency policy, upgrade runbook, PHP 8.6 upgrade checklist Ownership assigned

Operational Impact

Maintenance. MIT, small stable surface, active release cadence (2.x maintenance branch alongside 3.x), PHP 8.6 already supported and now actively de-risked. 3.1.0 is a good datapoint: the maintainers shipped a forward-looking deprecation ahead of the PHP release that forces it, rather than letting consumers discover it at upgrade time. Risk remains maintainer concentration rather than abandonment — a tiny number of people steward a package that sits underneath essentially every PHP HTTP integration we own. Mitigation stays boring and effective: pin constraints, let Dependabot/Renovate drive patch uptake, keep a lockfile-diff review step so a transitive bump never lands unreviewed, and add the Is→State rename to the standing deprecation-cleanup backlog with a due cycle. A semver-major (future 4.0, where Is presumably goes away) is the event that should trigger a scheduled work item.

Support. Community support via GitHub issues; commercial support and coordinated security disclosure via the Tidelift subscription ([email protected]). If any of our fan-out code paths sit in a payment, eligibility, or compliance flow, that subscription is cheap insurance and gives us a named escalation path — worth folding into the vendor review rather than treating as a nice-to-have.

Scaling. Concurrency here scales latency, not CPU: it reduces wall-clock time per request while holding worker occupancy roughly constant or worse. Levers that matter:

  • Concurrency cap per integration, not per request. 3.0 adds concurrency config to Utils::all() and Each::of() — use it. Unbounded fan-out converts a latency win into a vendor 429 storm at peak traffic.
  • Timeout discipline. Per-call connect/read timeouts plus a group-level ceiling. A group is only as fast as its slowest member; one un-timed-out call holds the whole wait().
  • Worker budget. Under FPM, fan-out trades sequential latency for concurrent outbound sockets from the same worker pool. Model the socket/connection ceiling before enabling on the highest-traffic endpoint.
  • Octane caution. Long-lived workers retain any pending handlers and callbacks that were never drained. Load-test under Octane specifically, watching worker memory across thousands of requests.

Failure modes to design for explicitly:

Failure Symptom Control
Blocking wait() on a dead vendor Worker pool exhaustion, p99 collapse Hard group timeout; circuit breaker at the adaptor
Partial vendor failure Utils::all() rejects everything on one failure Use settle/some where degradation is acceptable; define which
Assumed cancellation Cost/latency not reduced; vendor work continues Document that cancel() is cooperative; rely on timeouts instead
getState() / State::* branching Silent logic errors on promise-wrapped promises Code-review lint rule targeting the predicate, not the class name; prefer wait() + try/catch
Deprecated Is::* usage left in place Noisy CI, then hard failure at next major Mechanical Is→State sweep in this cycle
Unhandled rejection RejectionException/AggregateException surfacing as a 500 Map to domain exceptions at the adaptor boundary
Promise crossing a queue Serialization rejected at runtime Contract rule: jobs receive scalars/DTOs only
Handler leak under Octane Growing worker RSS Drain the queue; soak test

Ramp-up. The API takes an afternoon; the semantics take a sprint. Budget for: one reference implementation, one short internal write-up covering the state/wait() caveat and the cancellation limitation, and a review checklist. Add one line to that write-up making clear that State is a rename, not a correction — otherwise the rename actively spreads the defect pattern under a name that sounds safer. Do not assume a mid-level engineer who has used Http::get() will infer that then() callbacks are lazy and that nothing executes until wait() — that misunderstanding produces code that looks asynchronous and behaves sequentially, which is the most expensive outcome here: added complexity, zero latency gain.

TPM recommendation. Unchanged in substance, plus one cheap addendum. Fund this as a small, time-boxed latency optimization on a named set of fan-out endpoints, with a measured baseline as the entry gate and a p95 delta as the exit gate — not as a platform-wide "adopt async" initiative. Keep the promise type behind an application-owned runner contract, cap concurrency centrally, and treat the Tidelift subscription as a decision to make once, now, while the scope is still small. Separately, attach the Is→State sweep to the next dependency-bump PR as a zero-cost tack-on; do not let it justify a project of its own, and do not let it be the reason anyone touches the async code paths prematurely.

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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle