Thunks And Closure
Thunks don't fix trust and don't make code meaningfully more linear. What they do fix is bigger than it sounds: eliminating time itself as a factor of state. That single idea is the entire conceptual foundation a Promise is built on.
The last post built an active thunk that bridges a real race condition with two closure variables. Worth being honest about what that pattern actually earned, and what it didn't, before this chapter moves on.
What Thunks Don't Fix
A thunk's callback is still a plain callback underneath. Every trust problem from a few posts back, called twice, called never, called with the wrong data, is still sitting there, completely unaddressed. Thunks were never designed to fix inversion of control, and they don't.
They don't fix reasonability either, not really. The nested inventoryThunk(cb => shippingThunk(cb => ...)) chain from the last post is still nested. It reads a little cleaner than a raw scanning loop over shared state, but it isn't meaningfully more linear or sequential-looking than a plain callback chain. The eyes still have to jump inward at every level.
What Thunks Actually Fix
Here's the part that's easy to undersell: a thunk eliminates time as a complicating factor of state.
Think about what made the dashboard exercise hard in the first place, not the fetching, the coordinating, keeping track of what's arrived, what hasn't, and in what order. Understanding how state changes over time is the single hardest part of reasoning about any program. It's not impossible, but it's genuinely the most complex thing you manage.
An active thunk takes that complexity and solves it exactly once, inside the thunk itself, then never makes you think about it again. Every place that consumes inventoryThunk just calls it with a callback. It never has to ask whether the value already exists or is still in flight, that question got answered once, in the thunk's own closure, and the answer is baked in from then on.
ExpandThunks leave trust and reasonability unsolved, but they do solve something real: time itself, abstracted away once instead of re-solved at every call site.
That's not nothing. The plumbing, the closure bridge handling "did the value arrive first, or did the callback," gets written once. Everywhere that plumbing gets used afterward is just a callback call, with the timing question already answered for it.
Why This Was Worth the Detour
None of this chapter has been about thunks themselves being the destination. They're the conceptual bridge to understanding what a Promise actually is.
A Promise is, underneath its friendlier API, exactly this: a time-independent wrapper around a value. The closure-bridging logic from the last post, the two variables catching whichever race outcome actually happens, is precisely what a Promise implementation does internally, just built once, formalized, and handed to you by the language instead of something you write by hand for every value you wrap.
That's why this detour mattered. Understanding thunks first makes the Promise API look like a formalization of something you already understand, not a new piece of magic to memorize.
The Essentials
- Thunks don't solve inversion of control. A thunk's callback carries every trust problem a plain callback does.
- Thunks don't make code meaningfully more linear. The consuming code is still nested, still callback-shaped.
- Thunks do solve something real: time as a complicating factor of state. The hardest part of async reasoning gets handled once, inside the thunk, instead of at every call site.
- A Promise is this exact pattern, formalized. A time-independent value wrapper, with a standard API instead of hand-built closure bridging.
- Thunks aren't replaced by Promises, they're the foundation underneath them. Understanding one makes the other make sense.
Time to see what that formal version actually looks like. The next post starts Promises for real.
Keep reading