Fetch API Stream Locking: Understanding the 'Locked or Disturbed' Error

Found a fascinating behavior of the Fetch API while debugging a production streaming issue today. If you work with LLMs or real-time data, you need to know this: The Scenario I had a backend endpoint that was "polymorphic": 1. If credentials were low, it returned a JSON error. 2. If everything was fine, it returned a Text Stream (for that snappy AI typing effect). The Bug I tried to parse the response as JSON first to check for errors. If that failed, I assumed it was a stream and tried to read it. Result? The stream was empty, and the browser threw a "Locked or Disturbed" error. Why does this happen? A Fetch response body isn't just a variable; it’s a ReadableStream. Think of it like a one-way conveyor belt: A. The "Lock": Once you call .json(), .text(), or .blob(), the browser attaches a "reader" to that conveyor belt. To protect memory, a stream can only have one reader at a time. B. No Rewind: Once those bytes are pulled off the belt and turned into a JavaScript object, they are gone. You can't "rewind" the belt to read them again as a stream. The stream is now "disturbed." The Fix: .clone() If you need to "peek" at the data without destroying the original stream, you must use the .clone() method. const response = await fetch('/api/stream'); // Create an identical twin of the response const clone = response.clone(); try { // Use the clone to "peek" for JSON errors const data = await clone.json(); handleError(data); } catch { // If parsing the clone fails, the ORIGINAL response is still // "undisturbed" and ready to be streamed to the UI! return response.body.getReader(); } The Lesson: Streams are built for performance and memory efficiency, not flexibility. If you need to read a response body twice, .clone() is your best friend. #WebDev #JavaScript #Frontend #CodingTips #SoftwareEngineering #Fetch #BrowserAPI

To view or add a comment, sign in

Explore content categories