The error you already know
You wrote an async function, got your reply back, and used it like the string it returns. In Python that would have worked, or at least it would have warned you. Here the value flowing forward is a promise of that string, and the compiler catches it the moment you hand it to code that wants the real thing.
// fetchReply is async, so it hands back// a promise, not the text itself.// You called it without await and// passed the result straight on.async function fetchReply(prompt: string,): Promise<string> {return await callModel(prompt);}function show(text: string) {console.log(text.trim());}const reply = fetchReply("summarize this thread",);show(replyArgument of type 'Promise<string>' is not assignable to parameter of type 'string'.);
fetchReply returns Promise<string>, and show wants a string. Without the await, the promise itself flows into the call, and the two types no longer line up. This is the lucky version of the bug. The mismatch is loud and the compiler stops you on the spot.
The unlucky version is quieter. Hand that same promise to something that accepts any, push it into an array, log it, and nothing complains. The value is a perfectly valid Promise<string>, so it drifts on until something finally expects the string and cannot find it, often far from the line where the await went missing. Python at least grumbles that a coroutine was never awaited. TypeScript lets a floating promise travel until the shape stops matching.
reply.ts Argument of type 'Promise<string>' is not assignable to parameter of type 'string'.
Watch the habit become the idiom
The fix is the same await you already type, with the loop machinery taken away. The asyncio version you know crosses over, and the run and gather ceremony falls off along the way.
Step through it at your own pace. Each click is one move.
Step 1 of 4
Good async Python. gather fans the calls out and asyncio.run drives the loop.
import asyncio
async def fetch(p):
return await call_model(p)
async def main():
a, b = await asyncio.gather(
fetch("one"),
fetch("two"),
)
asyncio.run(main())Here's what each move does.
- Start in Python, where
asyncio.gatherfans the calls out andasyncio.runspins up the loop that drives them. Nothing runs until that loop does. - Cross to TypeScript and the naive port awaits each call on its own line, so the second one waits for the first to finish. Correct, and slower than it needs to be.
- Here is the machine difference. Calling an async function returns a promise that is already running. Hold two of them and both are in flight, no loop to start.
Promise.allis the newgather, except nothing wraps it the wayrunwrappedmain. You await the pair at once, unpack the results, and the event loop you never see does the rest.
That eagerness is the whole shift. A Python coroutine sits still until the loop awaits it. A promise is already running by the time you can hold it. Calling the function started the work, so there is no loop to reach for.
Now make the compiler agree
Here is a handler with both halves of the problem. The first reply is never awaited, so the line that joins the two replies refuses to add a promise to a string. And the two model calls run one after the other when neither needs the other's result. Add the missing await, then start both calls together with Promise.all and await the pair so the second is not stuck behind the first. The joined result should be a plain string.