Skip to content
DevMeme
Knock-knock joke fails when the caller is an async function
Languages Post #34, on Jan 29, 2019 in TG

Knock-knock joke fails when the caller is an async function

Why is this Languages meme funny?

Level 1: The Joke That Couldn't Wait Its Turn

Imagine playing a knock-knock joke with a friend who's so impatient they shout the answer before you've even asked the question. That's all this is: a computer told to "go do this task whenever you get a chance" sometimes finishes at a weird moment — like ordering pizza, and the delivery guy hands it to you before you've finished dialing the phone. It's funny because everyone who programs has had the computer answer them out of order at least once, and all you can do is laugh and put the conversation back in the right sequence.

Level 2: What async Actually Does

A few terms doing the heavy lifting here:

  • An async function is a function that can pause itself while waiting for something slow — a network request, a file read, a database query — and let the rest of the program keep running in the meantime. It returns a promise: an IOU for a value that doesn't exist yet.
  • await is how you say "actually stop here until the IOU pays out." Without it, the function just keeps going and the result shows up later, whenever it's ready.
  • The event loop is the dispatcher that decides what runs next. It's why JavaScript can juggle many tasks on one thread: nothing blocks; things just take turns.

The meme is what happens when you don't await. Your first week with fetch() probably looked like this:

let user;
getUser().then(u => user = u);  // - An async function
console.log(user);              // - Who's there? ... undefined

The console.log runs immediately, before the data arrives. The fix is making the order explicit:

const user = await getUser();   // now the answer waits for the question
console.log(user);

Out-of-order output in your console, undefined where data should be, race conditions in tests — these are rites of passage. The meme is just that rite of passage carved into rustic wood, like a motivational poster from a very tired team lead.

Level 3: The Punchline Resolved Before Its Await Point

  • Knock knock
  • An async function
  • Who's there?

Every senior engineer reads this and hears the ghost of a production incident. The joke works because it violates the one contract a knock-knock joke has — strict request/response ordering — in exactly the way async code violates the contract your eyes assume when reading top-to-bottom source. The setup fires, the response arrives before the question that solicits it, and somewhere a log aggregator shows user fetched timestamped before fetching user....

This is the entire genre of bugs that async_await was supposed to fix and instead made subtler. The classic failure modes all live in this meme:

  • The forgotten await — you call an async function, ignore the returned promise, and continue as if the work finished. The punchline executes "whenever," which in staging is always and in production is during the demo.
  • The race condition — two operations with an implicit ordering assumption and no explicit synchronization. It passes tests because the test machine's I/O is fast enough that the answer usually lands after the question.
  • forEach(async ...) — the beloved trap where you fire N punchlines concurrently and the function returns before any of them resolve, leaving the "Who's there?" of your cleanup code talking to an empty room.

The cruel part, and the reason the meme is "too real" rather than merely cute: async bugs are read-time invisible. The code looks sequential. Code review approves it because humans parse dialogue order, not scheduler order. That gap between what the source spells and what the event loop performs is where on-call weekends go to die — and it's why Promise.all, for...of with await, and structured concurrency patterns exist: they're all ways of forcing the joke to be told in order.

Level 4: Microtasks Before Macrotasks

The genius of this three-line joke is that it compiles down to a precise statement about continuation scheduling. In JavaScript's concurrency model, an async function runs synchronously until its first await, at which point the rest of the function body is captured as a continuation and queued — not on the timer-driven macrotask queue, but on the microtask queue, which drains completely before the event loop yields to anything else. The punchline arriving early is exactly what happens when a continuation resolves against an already-settled promise: the "answer" is scheduled before the conversational "question" ever gets its turn on the stack.

Formally, async/await is syntactic sugar over a CPS transformation (continuation-passing style) — the compiler rewrites your linear-looking function into a state machine, the same trick C# pioneered with its async rewriter and that Rust performs when desugaring async fn into a Future with poll states. The joke's scrambled ordering is the visible artifact of that rewrite: source order and execution order are decoupled the moment you suspend. Hardware engineers will recognize the deeper rhyme — CPUs have done out-of-order execution since the Tomasulo algorithm in the 1960s, retiring instructions in program order only at the commit stage. JavaScript's event loop offers no such reorder buffer for your dialogue. There is no commit stage that puts "Who's there?" back in front. Once you've gone asynchronous, happens-before relationships exist only where you explicitly await them — which is precisely the structured-concurrency argument: unordered punchlines are just data races wearing a clown nose.

Description

The meme shows a light brown wooden plank background with three bullet-pointed lines of white monospaced text. Line 1 reads “ - Knock knock”. Line 2 reads “ - An async function”. Line 3 reads “ - Who's there?”. The punchline never arrives, humorously illustrating that an asynchronous function returns immediately and the caller continues without waiting for the response, parodying the classic knock-knock joke structure. This visual gag pokes fun at async/await behavior and the need to await a Promise before you get a value back, something senior JavaScript developers encounter regularly when dealing with non-blocking code

Comments

7
Anonymous ★ Top Pick Async knock-knock: Product asks “Who’s there?”, backend returns Promise<punchline>, the event loop queues it, two sprints pass, roadmap pivots, and six months later Sentry logs an UnhandledPromiseRejection: door already closed
  1. Anonymous ★ Top Pick

    Async knock-knock: Product asks “Who’s there?”, backend returns Promise<punchline>, the event loop queues it, two sprints pass, roadmap pivots, and six months later Sentry logs an UnhandledPromiseRejection: door already closed

  2. Anonymous

    This is the same async joke we've been debugging in production for years, except now it's wrapped in a useEffect with no cleanup function and somehow still getting 10k upvotes on Reddit

  3. Anonymous

    The joke landed before the setup - classic unawaited promise. Reviewer comment: 'works on my machine, but only because the punchline resolved fast that day.'

  4. Anonymous

    This perfectly captures the existential crisis of async programming: you knock, but by the time the Promise resolves, you've already moved on to the next tick of the event loop. The real punchline? In production, 'Who's there?' times out after 30 seconds, gets retried three times, and eventually returns a 504 Gateway Timeout

  5. Anonymous

    Only in JavaScript can a knock-knock return Promise<pending>; the punchline sits on the microtask queue and arrives after the retro

  6. Anonymous

    Async knock-knock: ask “who’s there?” without await and you get [object Promise]; await it and the answer arrives eventually consistent with your patience

  7. Anonymous

    The punchline? Still pending in a .then() three levels deep, timing out in prod as usual

Use J and K for navigation