<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Javascript on ErrorZap</title><link>https://errorzap.com/tags/javascript/</link><description>Recent content in Javascript on ErrorZap</description><image><title>ErrorZap</title><url>https://errorzap.com/og.png</url><link>https://errorzap.com/og.png</link></image><generator>Hugo</generator><language>en-US</language><copyright>ErrorZap</copyright><lastBuildDate>Mon, 02 Nov 2026 17:00:00 -0700</lastBuildDate><atom:link href="https://errorzap.com/tags/javascript/index.xml" rel="self" type="application/rss+xml"/><item><title>The PWA That DDoSed Itself With Polling</title><link>https://errorzap.com/posts/the-pwa-that-ddosed-itself-with-polling/</link><pubDate>Mon, 02 Nov 2026 17:00:00 -0700</pubDate><guid>https://errorzap.com/posts/the-pwa-that-ddosed-itself-with-polling/</guid><description>I asked a tiny dashboard for reassurance ten times a second. It answered by manufacturing its own outage.</description><content:encoded><![CDATA[<p>The dashboard was supposed to make the system feel calmer.</p>
<p>One screen. A few useful numbers. A green dot when the service was reachable. The sort of little home-lab panel that says, <em>Everything is fine; go do literally anything else.</em></p>
<p>Instead, it became the most anxious machine in the house.</p>
<p>The first version polled its backend roughly ten times a second. On a fast local network, the requests usually returned before anyone noticed. On cellular data, Wi-Fi roaming, or a sleepy phone, a response could take longer than the interval between requests. The dashboard did not wait. It launched another request, then another, then another.</p>
<p>Soon the browser was running a tiny stampede:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>tick 1: request A --------------------&gt;
</span></span><span style="display:flex;"><span>tick 2:   request B ------------------&gt;
</span></span><span style="display:flex;"><span>tick 3:     request C ----------------&gt;
</span></span><span style="display:flex;"><span>tick 4:       request D --------------&gt;
</span></span><span style="display:flex;"><span>                       &lt;--- C fails
</span></span><span style="display:flex;"><span>              &lt;--- A succeeds
</span></span></code></pre></div><p>Because responses arrived out of order, an old failure could overwrite a newer success. The service was fine. The dashboard announced that it was dead.</p>
<h2 id="a-green-dot-can-be-a-denial-of-service-tool">A green dot can be a denial-of-service tool</h2>
<p>Polling looks harmless because every request is small. The danger is multiplication.</p>
<p>If a new request starts every 100 milliseconds but a slow network takes two seconds to answer, twenty requests can be in flight at once. Add retries and multiple open tabs and the monitoring client becomes load generation. It is the digital equivalent of calling a restaurant ten times a second to ask whether the phone system works.</p>
<p>The problem was not &ldquo;JavaScript is bad&rdquo; or &ldquo;cellular is unreliable.&rdquo; The problem was that the design had no concept of an unfinished question.</p>
<p>The fix was a single-flight rule:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>if a check is already running:
</span></span><span style="display:flex;"><span>    do not start another one
</span></span><span style="display:flex;"><span>else:
</span></span><span style="display:flex;"><span>    run one check with a timeout
</span></span><span style="display:flex;"><span>    record the result
</span></span><span style="display:flex;"><span>    schedule the next check afterward
</span></span></code></pre></div><p>That one rule changed the system from a metronome into a conversation.</p>
<h2 id="one-miss-is-not-an-outage">One miss is not an outage</h2>
<p>The second mistake was treating every failed request as a verdict.</p>
<p>Phones change networks. Radios wake up. DNS caches expire. A tab disappears into the background and returns with its timers distorted. One missed check is evidence of one missed check. It is not proof that the watched service is down.</p>
<p>A useful status display needs memory:</p>
<ul>
<li>keep the last known-good result visible for a bounded grace period;</li>
<li>count consecutive failures rather than panicking on the first one;</li>
<li>show &ldquo;stale&rdquo; before &ldquo;dead&rdquo;;</li>
<li>require a fresh success before declaring recovery;</li>
<li>slow down when the tab is hidden or the device is offline.</li>
</ul>
<p>This is not hiding failures. It is refusing to promote noise into truth.</p>
<h2 id="monitoring-software-needs-a-nervous-system">Monitoring software needs a nervous system</h2>
<p>The best monitors are not the ones that ask the most questions. They are the ones that understand the cost and meaning of each answer.</p>
<p>A calm client has a timeout, a backoff policy, a maximum concurrency of one for each check, and a state machine more expressive than green versus red. It distinguishes <code>healthy</code>, <code>stale</code>, <code>degraded</code>, <code>offline</code>, and <code>unknown</code>. Most importantly, it does not let an older response travel backward through time and erase a newer one.</p>
<p>The final dashboard did less work and told the truth more often.</p>
<p>That is a recurring engineering lesson: when a monitoring screen screams, check whether the patient is sick—or whether the thermometer is stabbing it ten times a second.</p>
]]></content:encoded></item></channel></rss>