The Secret Life of JavaScript: The Ghost

 

The Secret Life of JavaScript: The Ghost

Mastering Stale-While-Revalidate

#JavaScript #WebPerformance #ServiceWorkers #Caching




Margaret is a senior software engineer. Timothy is her junior colleague. They work in a grand Victorian library in London — the kind of place where code quality is the unspoken objective, and craftsmanship is the only thing that matters.

Episode 28

The Double Refresh

Timothy slumped back in his chair, staring at a bug report that had just been escalated to "Critical."

His offline-ready Service Worker from last week had been a massive architectural success. The dashboard loaded instantly, and the offline dinosaur was a thing of the past. But now, there was a new, highly frustrating problem.

"The users are trapped in the past," Timothy explained as Margaret walked over with her morning dark roast. "The client updated their profile picture and changed some core data on the settings page. But when they navigate back to the dashboard, it still shows the old data. The only way they can see their updates is if they forcefully refresh the browser twice—once to get the stale cache, and a second time to finally see the new data. It is driving them crazy."

The Overzealous Maître D'

Margaret nodded slowly, recognizing the classic symptom immediately.

"You are experiencing the dark side of the Cache-First strategy," Margaret said. She pulled up the fetch event listener they had written for the Service Worker. "Last week, we hired a maître d' to stand at the front door and intercept network requests. We told him: if you have a copy of the menu in your cache, hand it to the customer immediately and do not bother asking the kitchen."

"Right. That makes it instantly fast and works offline," Timothy replied.

"Exactly," Margaret said. "But your maître d' is being overzealous. He is handing the customer yesterday's menu, and because he found it in his pocket, he never checks the kitchen to see if the chef added any new specials. You traded freshness for speed."

The Ghost

"So, I have to choose?" Timothy asked. "Either I fetch from the network and make the user wait, or I serve from the cache and show them stale data?"

"In computer science, we rarely accept a binary choice," Margaret smiled. She took a dry-erase marker and wrote three words on the whiteboard: Stale-While-Revalidate.

"We are going to teach the maître d' a new trick," she explained. "When the customer walks in, the maître d' will immediately hand them the cached menu from his pocket so they have something to look at instantly. But then, like a ghost, he will silently slip back to the kitchen, grab the absolute freshest menu, and quietly slide it into his pocket for the next time the customer visits."

"It gives us the ultimate illusion," Margaret tapped the board. "The user gets the instant load time of the cache, but the background network request ensures the application is in a state of eventual consistency."

The Silent Update

Timothy opened his sw.js file and modified his fetch interceptor to handle the asynchronous ghost request, making sure to add a guard so it only cached standard GET requests, not data mutations.

// sw.js - The Front Door Proxy
self.addEventListener('fetch', (event) => {
  // Only intercept GET requests; ignore POST/PUT/DELETE
  if (event.request.method !== 'GET') return;

  event.respondWith(
    caches.match(event.request).then((cachedResponse) => {
      
      // 1. The Ghost (Stale-While-Revalidate strategy)
      // Always fetch fresh data from the kitchen silently
      const fetchPromise = fetch(event.request).then((networkResponse) => {
        caches.open(CACHE_NAME).then((cache) => {
          // We must clone() the response because it can only be consumed once
          cache.put(event.request, networkResponse.clone());
        });
        return networkResponse;
      }).catch(() => {
        // Graceful degradation: if the network completely fails, 
        // the user still has the stale cache to fall back on.
        return cachedResponse; 
      });

      // 2. The Delivery: Hand the user the stale cache immediately if we have it.
      // If the cache is empty (first visit), wait for the network.
      return cachedResponse || fetchPromise;
    })
  );
});

Eventual Consistency

"Look at the elegance of that promise flow," Margaret said, pointing to the return statement. "If cachedResponse exists, the Service Worker returns it to the browser immediately. The UI renders in a fraction of a millisecond. But notice that fetchPromise is still executing in the background. It reaches out to the network, grabs the fresh data, and quietly updates the cache."

Timothy deployed the new Service Worker and ran a test.

He opened the dashboard. It loaded instantly from the cache. He went to another tab, changed his profile name from "Timothy" to "Tim," and clicked save. He switched back to the dashboard tab and clicked refresh.

The dashboard loaded instantly again, but this time, the name said "Tim." There was no double refresh required. The background "ghost" request from the previous visit had silently updated the cache, ensuring the new data was waiting for him exactly when he needed it.

He had achieved the holy grail of web performance: instant UI with eventual consistency.


Aaron Rose is a software engineer and technology writer at tech-reader.blog

Catch up on the latest explainer videos, podcasts, and industry discussions below.


Comments

Popular posts from this blog

Insight: The Great Minimal OS Showdown—DietPi vs Raspberry Pi OS Lite

The New ChatGPT Reason Feature: What It Is and Why You Should Use It

Raspberry Pi Connect vs. RealVNC: A Comprehensive Comparison