<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aftabul Islam</title>
    <description>The latest articles on DEV Community by Aftabul Islam (@aihimel).</description>
    <link>https://dev.to/aihimel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F542687%2F53248f72-b418-4e9e-a8ae-8737814f4fff.jpeg</url>
      <title>DEV Community: Aftabul Islam</title>
      <link>https://dev.to/aihimel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aihimel"/>
    <language>en</language>
    <item>
      <title>Laravel Waiting Request</title>
      <dc:creator>Aftabul Islam</dc:creator>
      <pubDate>Sat, 23 May 2026 06:50:00 +0000</pubDate>
      <link>https://dev.to/aihimel/laravel-waiting-request-27o4</link>
      <guid>https://dev.to/aihimel/laravel-waiting-request-27o4</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;You are processing some data through background job. But before the processing is done, another request had been made to read the related data.&lt;/p&gt;

&lt;p&gt;In this case you are either providing a historic data or serving wrong information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;Holding the request until the job is executed, could be the simplest solution.&lt;br&gt;
I am not saying it is the only solution, but the simplest one.&lt;/p&gt;
&lt;h2&gt;
  
  
  Some Scenarios
&lt;/h2&gt;

&lt;p&gt;Lets discuss about some possible scenarios.&lt;/p&gt;
&lt;h3&gt;
  
  
  Booking Job
&lt;/h3&gt;

&lt;p&gt;When a user request to book a resource between two specifics dates. Let's assume that it is done by a job. So it might take some time in production load.&lt;br&gt;
In the meantime if another request is asking for that specific users booking data.&lt;/p&gt;
&lt;h3&gt;
  
  
  File Importing Job
&lt;/h3&gt;

&lt;p&gt;User uploads a file like CSV or XML, you have accepted the file but it also needs processing, which should be done in by a job.&lt;br&gt;
If the user ask for the status of the CSV resource in another request.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Package
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://packagist.org/packages/aihimel/laravel-waiting-request" rel="noopener noreferrer"&gt;&lt;code&gt;aihimel/laravel-waiting-request&lt;/code&gt;&lt;/a&gt; is a small Laravel package that solves exactly this — it lets one request &lt;em&gt;park&lt;/em&gt; until another piece of work (a job, a sync, a long-running controller action) signals that the resource is ready to read.&lt;/p&gt;
&lt;h3&gt;
  
  
  Install
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require aihimel/laravel-waiting-request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Optionally publish the config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan vendor:publish &lt;span class="nt"&gt;--tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"waiting-request-config"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;The package exposes a tiny API around four ideas: &lt;strong&gt;block&lt;/strong&gt;, &lt;strong&gt;wait&lt;/strong&gt;, &lt;strong&gt;check&lt;/strong&gt;, &lt;strong&gt;resolve&lt;/strong&gt;. Under the hood it is backed by your Laravel cache — no extra infrastructure, no queue plumbing.&lt;/p&gt;

&lt;p&gt;A blocker is identified by a &lt;em&gt;class path&lt;/em&gt; and a &lt;em&gt;resource id&lt;/em&gt;. That pair becomes a unique cache key, so blockers are per-resource (booking &lt;code&gt;42&lt;/code&gt; does not interfere with booking &lt;code&gt;43&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Aihimel\LaravelWaitingRequest\Facades\LWRequest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 1. Block — call this where the background work *starts*&lt;/span&gt;
&lt;span class="nc"&gt;LWRequest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;addBlocker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Booking&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$booking&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Wait — call this in the request that wants to read the resource&lt;/span&gt;
&lt;span class="nv"&gt;$resolved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LWRequest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;whenResolved&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Booking&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$booking&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$resolved&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;BookingResource&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$booking&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;fresh&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'message'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Still processing, try again'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Resolve — call this when the background work finishes&lt;/span&gt;
&lt;span class="nc"&gt;LWRequest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;resolveBlocker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Booking&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$booking&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also peek without waiting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LWRequest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;isBlocked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Booking&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$booking&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// resource is mid-flight&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Applying it to the scenarios
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Booking job.&lt;/strong&gt; The controller that accepts the booking calls &lt;code&gt;addBlocker(Booking::class, $id)&lt;/code&gt; and dispatches the job. The job calls &lt;code&gt;resolveBlocker(...)&lt;/code&gt; in its &lt;code&gt;handle()&lt;/code&gt; (or in a &lt;code&gt;finally&lt;/code&gt; block). Any reader that hits &lt;code&gt;GET /bookings/{id}&lt;/code&gt; in the meantime calls &lt;code&gt;whenResolved(...)&lt;/code&gt; first and only reads the model once the writer is done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File importing job.&lt;/strong&gt; Same shape: &lt;code&gt;addBlocker(Import::class, $import-&amp;gt;id)&lt;/code&gt; when the upload is accepted, &lt;code&gt;resolveBlocker(...)&lt;/code&gt; when the parser finishes (success &lt;em&gt;or&lt;/em&gt; failure — both should release). The status endpoint calls &lt;code&gt;whenResolved(...)&lt;/code&gt; so the client gets a settled answer instead of a half-imported snapshot.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sensible defaults you can tune
&lt;/h3&gt;

&lt;p&gt;Every knob lives in &lt;code&gt;config/waiting-request.php&lt;/code&gt; and is overridable via env:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Config&lt;/th&gt;
&lt;th&gt;Env&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cache_prefix&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LW_REQUEST_CACHE_PREFIX&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lw_request_&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Namespace for cache keys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;timeout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LW_REQUEST_MAX_WAITING_TIME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How long &lt;code&gt;whenResolved()&lt;/code&gt; waits before giving up (seconds)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;check_interval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LW_REQUEST_CHECK_INTERVAL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;250&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Poll interval inside &lt;code&gt;whenResolved()&lt;/code&gt; (milliseconds)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_blocking_time&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LW_REQUEST_MAX_BLOCKING_TIME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Max lifetime of a blocker before it auto-expires (seconds)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;addBlocker()&lt;/code&gt; takes an optional third argument so you can bump the TTL per call when you know a particular job runs longer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;LWRequest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;addBlocker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Import&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$import&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 2 minutes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why the blocker has a lifetime
&lt;/h3&gt;

&lt;p&gt;If a job crashes before calling &lt;code&gt;resolveBlocker()&lt;/code&gt;, you do &lt;strong&gt;not&lt;/strong&gt; want readers to wait forever. From v2.x every blocker carries a Unix expiry timestamp. The next &lt;code&gt;isBlocked()&lt;/code&gt; / &lt;code&gt;whenResolved()&lt;/code&gt; call after that timestamp will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Forget the cache entry, and&lt;/li&gt;
&lt;li&gt;Emit &lt;code&gt;Log::warning('Waiting-request blocker expired without being resolved', [...])&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So even if your job dies, traffic recovers on its own and you get a log line telling you it happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do's and Don'ts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Do release the blocker in &lt;code&gt;finally&lt;/code&gt;.&lt;/strong&gt; Wrap your job body so a thrown exception still hits &lt;code&gt;resolveBlocker()&lt;/code&gt;. Auto-expiry is a backstop, not a happy path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do set &lt;code&gt;max_blocking_time&lt;/code&gt; to comfortably exceed your worst-case job duration.&lt;/strong&gt; If your import averages 8s and worst-cases at 25s, a 10s default will auto-release while the job is still running — defeating the lock.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do tune &lt;code&gt;timeout&lt;/code&gt; to match your UX budget.&lt;/strong&gt; If a client is willing to wait 2s for a synchronous response, set &lt;code&gt;timeout=2&lt;/code&gt;; do not let &lt;code&gt;whenResolved()&lt;/code&gt; hold an HTTP worker for 30s.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do flush the cache when upgrading from 1.x to 2.x.&lt;/strong&gt; Pre-upgrade values stored as &lt;code&gt;true&lt;/code&gt; will be read as &lt;code&gt;1&lt;/code&gt;, treated as already-expired, and produce a one-time burst of warning logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do treat a &lt;code&gt;false&lt;/code&gt; return from &lt;code&gt;whenResolved()&lt;/code&gt; as "still pending".&lt;/strong&gt; Respond with &lt;code&gt;202 Accepted&lt;/code&gt; (or similar) and let the client poll — do not pretend the data is ready.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Don't
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Don't put &lt;code&gt;isBlocked()&lt;/code&gt; on a hot, read-only path you expect to be side-effect-free.&lt;/strong&gt; It evicts expired entries and writes a log line. That is intentional, but worth knowing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't use it as a distributed mutex for &lt;em&gt;writes&lt;/em&gt;.&lt;/strong&gt; This package is for &lt;em&gt;readers waiting on writers&lt;/em&gt; on a best-effort basis. If two writers race, &lt;code&gt;Cache::add()&lt;/code&gt; will reject the second &lt;code&gt;addBlocker()&lt;/code&gt; (it returns &lt;code&gt;false&lt;/code&gt;), but the package does not give you queueing, fairness, or strict mutual exclusion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't share a single blocker across unrelated resources.&lt;/strong&gt; Key it by the real resource (&lt;code&gt;Booking::class + $id&lt;/code&gt;), not by something coarse like the user id, or you will block requests that have nothing to do with each other.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't forget the cache driver matters.&lt;/strong&gt; &lt;code&gt;array&lt;/code&gt; or &lt;code&gt;file&lt;/code&gt; drivers will not work across processes. In production, use &lt;code&gt;redis&lt;/code&gt; / &lt;code&gt;memcached&lt;/code&gt; so the worker that resolves the blocker and the web process that is waiting actually share the same cache.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't lean on &lt;code&gt;whenResolved()&lt;/code&gt; from a queue worker.&lt;/strong&gt; Polling inside a worker burns a worker slot. Workers should &lt;em&gt;resolve&lt;/em&gt; blockers, not &lt;em&gt;wait&lt;/em&gt; on them.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;That's the whole package — a couple of facade calls, a cache key per resource, and a sane expiry so nothing wedges. If you've ever shipped a &lt;code&gt;?retry=true&lt;/code&gt; hack or a sleep-and-pray in a controller, this is the cleaner version of that.&lt;/p&gt;

&lt;p&gt;Source &amp;amp; issues: &lt;a href="https://github.com/aihimel/laravel-waiting-request" rel="noopener noreferrer"&gt;github.com/aihimel/laravel-waiting-request&lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>laravel</category>
      <category>php</category>
    </item>
  </channel>
</rss>
