62% of JavaScript developers report wrestling with fetch’s two-step JSON dance in the Stack Overflow 2023 survey — that’s hours lost yearly on what should be a one-liner.
And here’s the fix that’s got my attention: decorating the Promise itself. No wrappers. No subclasses. Just native Response semantics, plus .get(‘/api/todos/1’).json() bliss.
ffetch, this featherweight fetch wrapper, nails it. Built for plugin ecosystems where purity matters — think no instanceof surprises or framework hiccups.
But does it hold up under real-world scrutiny? Let’s break it down.
Why Fetch Ergonomics Still Sucks in 2024
Native fetch works. await response.json() delivers. Yet codebases groan under repetition. Ky? Superb ergonomics, 12k+ stars on GitHub, but its custom Response clone trips up tools expecting vanilla objects.
Wrappers promise relief — until they don’t. Suddenly, your Promise passes lint checks but fails in Next.js middleware.
The usual answer is a wrapper class or a custom Promise subclass. Both work, but both carry a hidden cost: you are now responsible for whatever happens when you swap out the native Response for your own abstraction. instanceof checks break. Framework integrations that inspect the response directly can behave unexpectedly.
That’s straight from the ffetch dev’s playbook. Spot on.
This decoration sidesteps it entirely. Promises are objects. Slap properties on ‘em at runtime. Boom.
The Code That Makes .json() Stick
Look at attachResponseShortcuts. It grabs a Promise, defines non-enumerable, locked-down props for json, text, blob — you name it.
Each one’s a thin .then() forwarder to Response methods. Parsing? Native. Errors? Handled by the runtime. No reinvention.
function attachResponseShortcuts(promise: Promise<Response>) {
const descriptor = (fn: (r: Response) => unknown) => ({
value: function (this: Promise<Response>) {
return this.then(fn)
},
enumerable: false,
writable: false,
configurable: false,
})
Object.defineProperties(promise, {
json: descriptor((r) => r.json()),
// etc.
})
return promise
}
enumerable: false keeps ‘em out of Object.keys() and JSON.stringify(). DevTools? They’ll show for debugging. writable: false blocks accidental overwrites — safety first, even if it irks test hackers.
Promise stays pristine. await it, get Response. No prototype pollution. No chain breakage.
Smart.
Guards against double-decoration with a Symbol marker. Collision-proof. Perfect for plugin chains where multiple libs might poke the same Promise.
Can Promise Decoration Scale to Big Codebases?
Yes — with caveats. GitHub stars for similar libs like ky (12k), undici (8k) show hunger for this. ffetch? Early days, but the technique screams adoption.
Market dynamic: JS runtimes fragment — Node, Deno, browsers. Native fidelity wins. Wrappers lose when Vercel or Cloudflare inspect responses.
My take? This edges out competitors. Ky’s great until it isn’t. Here, ergonomics layer on without friction.
Edge case: Strict mode or minifiers might choke on dynamic props? Nah, Object.defineProperties is battle-tested since ES5.
But writable: false bites in mocks. Tradeoff favors runtime safety — aligns with libs like React’s frozen objects.
Historical parallel — remember Lodash’s chain methods? Attached without subclassing arrays, dodging jQuery’s mess. Same vibe: DX boost, zero breakage.
Prediction: Bun or Deno snags this by 2025. Fetch polyfills standardize it. Native semantics + sugar? Unstoppable.
Hype Check: Is ffetch Overpromising?
Not really. Dev admits it’s for convenience plugins, not revolution. No bold claims.
Critique: Docs could warn louder on Symbol overhead (negligible) or IE11 death (who cares?).
Still, in plugin land — where hooks chain — this shines. Multiple decorators? Symbol check prevents throws.
if (promise[DECORATED]) return promise; else decorate and stamp it.
Clean.
Tests? I’d add ‘em for proxy edge cases, but the thinness minimizes surface.
Real-World Wins and Gotchas
Win one: Codebases shrink. That 62% boilerplate stat? Vanishes.
Win two: Plugins compose. Your auth hook decorates, then serializer — no conflicts.
Gotcha: DevTools expansion shows props (good for debug). JSON.stringify skips ‘em (perfect).
Another: this.then(fn) chains correctly, but if callers expect Promise subclass methods? They won’t find ‘em — by design.
Bold call — corporate JS teams overlook this at peril. As PWAs balloon, fetch fatigue kills velocity. ffetch’s path? Pragmatic gold.
Why This Matters for JS Fetch Wars
Fetch wrappers war: ky ergonomic, undici performant, now ffetch pure. Market tilts to purity as frameworks ossify.
Data: npm dl for ‘fetch’ variants up 40% YoY (npm trends). Devs crave one-liners without regret.
ffetch positions smart — lightweight, semantic-safe. Watch it climb.
🧬 Related Insights
- Read more: AisthOS: The OS That Turns Sensors into Structured Gold, Not Garbage Data
- Read more: Vault Radar 2025: The Quiet Revolution in Secrets Visibility That’s Sneaking Past the Hype
Frequently Asked Questions
How do you add .json() to fetch Promises in JavaScript?
Use Object.defineProperties on the Promise to attach non-enumerable methods that .then() to Response.json(). See ffetch’s attachResponseShortcuts.
Does decorating Promises break native fetch behavior?
No — await still yields plain Response. Props don’t touch resolution, prototype, or standard ops.
Is ffetch’s Promise decoration safe for production plugins?
Yes, with Symbol guards against duplicates. Locked props prevent overwrites, prioritizing stability.
What’s the catch with non-configurable properties on Promises?
Can’t mock or override easily — deliberate for safety in shared plugin ecosystems.