What is async/await and why is my data undefined?
In one sentence:Async/await is JavaScript syntax where the async keyword marks a function as one that returns a promise and await pauses that function until a called promise settles into its real value, so asynchronous code reads top to bottom instead of chaining callbacks.
What it actually is
Some things your code asks for don’t come back instantly: a file on disk, a row in a database, a response from a server across the internet. JavaScript doesn’t freeze and wait for those. It hands you a promise right away instead, an object standing in for a value that hasn’t arrived yet. A promise sits in one of three states at any moment: pending (still waiting), fulfilled (the value showed up), or rejected (something went wrong instead).
async marks a function as one that works with promises: call it, and you get a promise back, even if the function’s own code just returns a plain number or string. await, used inside that async function, pauses execution at the promise until it settles, then hands you the real value in place of the promise wrapper.
Treat a promise like an IOU, not the cash itself. A bank teller hands you a receipt that says forty dollars is coming; it’s real, but you can’t buy groceries with it. await is standing at the counter until the teller hands you actual bills. Skip await and you walk out holding the receipt, trying to spend it as if it were money. That’s the bug behind both symptoms in this entry.
Why your AI just did this
Fetching data from an API endpoint, querying a database, or waiting on Supabase or Stripe are all asynchronous, so your agent reaches for async/await constantly. It’s the default way Cursor, Claude Code, and Lovable write anything that touches the network. The trouble starts when the agent writes the async function correctly but forgets await at the place that calls it, especially mid-edit across two files, when the definition keeps its internal await but the call site loses it. A quick follow-up like “fix this” can also make an agent shuffle code fast enough to drop the keyword without anyone noticing, since the code still runs with no error, just a silently wrong value. That dropped keyword is exactly the kind of edge case a working demo hides until you click the one path where timing actually matters.
When you’ll run into it
[object Promise] shows up when your code turns a promise directly into text, for example element.textContent = "Result: " + getData() or a template literal that embeds the call. JavaScript converts objects to strings by calling their default toString(), and a promise’s default string form is literally the text [object Promise]. In a React or Next.js app, putting an unresolved promise straight into JSX throws instead of printing: Objects are not valid as a React child (found: [object Promise]).
“Data is undefined, but the API works in the browser” is the same root cause without the label. You paste the endpoint’s URL into your browser and see clean JSON, but your app’s console.log(data) prints undefined or something unexpected. Somewhere the agent wrote const data = getData() instead of const data = await getData(). data holds the pending promise itself, not the response, and reading data.name off it returns undefined, because the promise carries its own pending, fulfilled, or rejected state, not your API’s fields.
Try to use await completely outside an async function and JavaScript refuses to run the file at all, with something like SyntaxError: await is only valid in async functions and the top level bodies of modules in Chrome and Node, or a slightly different phrasing in Firefox and Safari. The “modules” part matters: inside a JavaScript module (a file using import or export), await is allowed on its own at the top level, outside any function.
What to check
- Find every place the async function gets called, not just where it’s defined, and confirm
awaitsits right before each call - Confirm the function doing the awaiting is itself declared
async; without that, the keyword throws the syntax error above - Log
typeof dataright after the call. If it saysobjectand printingdatashows something likePromise { <pending> }, you’ve found the missingawait - In React or Next.js, avoid handing a promise straight to JSX. Resolve it first (inside
useEffectwithuseState, or withawaitin a server component) and render the resolved value - Check the function that calls your async function too: it needs its own
await(or must return the promise onward), or the same bug resurfaces one level up the chain
Say it like a dev
Instead of: “the page just prints [object Promise], but the data is right there” Say: “This is rendering an unresolved promise instead of its value. Find where await is missing on this call and add it before the result gets displayed.”
Instead of: “data comes back undefined here, but the API works fine when I open it directly” Say: “This function is async, so check every place it gets called for a missing await. The value is probably still a pending promise, not the response.”
Instead of: “just make the red error go away” Say: “This is a SyntaxError from using await outside an async function. Wrap the calling code in an async function instead of deleting the await.”
People actually ask
“Why does my page show [object Promise] instead of the actual data?”
Your code turned a promise into text before it resolved, usually through string concatenation or a template literal wrapped around a call that needed await. JavaScript's default way of converting an object to text produces the literal string [object Promise]. Add await before the call, or resolve the promise first, and use the real value instead.
“Why is my data undefined when the API works fine in the browser?”
The endpoint itself is fine, the problem sits at the call site. Somewhere your code wrote const data = getData() instead of const data = await getData(), so data holds the pending promise object instead of the response, and reading a field off that promise returns undefined. Add the missing await where the function gets called.
“Can I use await outside of an async function?”
Await only works inside an async function, with one exception: the top level of a JavaScript module, a file using import or export, allows await on its own. Anywhere else, JavaScript refuses to run the file, throwing a SyntaxError about await only being valid in async functions and modules (the exact wording differs slightly between Chrome, Node, and Firefox).
Related terms
Checked against
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Bad_await
Just met this in a real session? Glim reads it in plain words.
Paste what your agent just did, a git diff, your terminal, or its summary, and Glim tells you what changed and what to check before you ship. Nothing stored.
Explain my session →