Question
How can I return the result from a function like foo() that performs an asynchronous operation?
I tried returning the value directly from inside the callback, and I also tried storing the result in a local variable and returning that variable after the asynchronous call. In both cases, the function returns undefined or the variable's initial value instead of the actual response.
Example using jQuery ajax:
function foo() {
var result;
$.ajax({
url: '...',
success: function(response) {
result = response;
// return response;
}
});
return result;
}
Example using Node.js fs.readFile:
function foo() {
var result;
fs.readFile('path/to/file', function(err, data) {
result = data;
// return data;
});
return result;
}
Example using fetch(...).then(...):
function foo() {
var result;
fetch(url).then(function(response) {
result = response;
// return response;
});
return result;
}
Why does this happen, and what is the correct way to work with and return values from asynchronous code in JavaScript?
Short Answer
By the end of this page, you will understand why asynchronous JavaScript functions cannot return their final result immediately, why undefined appears so often in this situation, and how to handle async results correctly using callbacks, Promises, and async/await.
Concept
JavaScript often performs slow tasks asynchronously so the program can continue running while it waits.
Common asynchronous operations include:
- HTTP requests
- Reading files
- Database queries
- Timers
- User interactions
The key idea is this:
- A synchronous function finishes its work before returning.
- An asynchronous function starts work now, but finishes later.
That is why this pattern does not work:
function foo() {
let result;
fetch('/api/data').then(function(response) {
result = response;
});
return result;
}
When return result runs, the fetch request has not finished yet. The callback inside .then(...) will run later, after the current function has already returned.
Returning inside the callback also does not return from foo():
fetch('/api/data').then(function(response) {
return response;
});
That return response only returns from the callback passed to .then, not from the outer foo() function.
This matters because real programs constantly depend on async work. If you do not understand this control flow, you will get bugs such as:
undefinedvalues- code running in the wrong order
- UI rendering before data arrives
- APIs sending incomplete responses
The correct solution is to structure your code so that the value is used after the asynchronous work completes. In JavaScript, the main approaches are:
- use a callback
- return a Promise
- use
async/await
Mental Model
Imagine ordering food at a restaurant.
A synchronous function is like walking to a vending machine:
- you press a button
- the item comes out immediately
- you leave with the item
An asynchronous function is like ordering food from a kitchen:
- you place the order
- the kitchen starts preparing it
- you do not get the meal instantly
- you get it later when it is ready
Your foo() function is trying to do this:
- Place the order
- Immediately leave with the food
That cannot work because the food is still being prepared.
Instead, you must either:
- give the restaurant a way to notify you later (callback)
- receive a claim ticket for the future meal (Promise)
- wait for the meal in a cleaner way (
await)
That is the core mental model: async code gives you the result later, not now.
Syntax and Examples
1. Using a callback
A callback is a function you pass in so it can be called when the async work is done.
function foo(callback) {
fs.readFile('path/to/file', 'utf8', function(err, data) {
if (err) {
callback(err, null);
return;
}
callback(null, data);
});
}
foo(function(err, result) {
if (err) {
console.error(err);
return;
}
console.log(result);
});
Here, foo does not return the file contents directly. Instead, it passes the result to callback when ready.
2. Returning a Promise
A Promise represents a value that will be available in the future.
function foo() {
return fetch('/api/data')
.then(function(response) {
return response.json();
});
}
foo().then(function(result) {
console.log(result);
});
Now foo() returns a Promise, not the final data itself.
3. Using async/await
async/await is built on Promises and often reads more like synchronous code.
async function foo() {
const response = await fetch('/api/data');
const data = await response.json();
return data;
}
async function run() {
const result = await foo();
console.log(result);
}
run();
This looks like foo() returns the data directly, but it actually returns a Promise automatically because it is an async function.
jQuery example
If you are using jQuery, you can work with the async result inside success or use the Promise-like object returned by $.ajax.
function foo() {
return $.ajax({
url: '/api/data'
});
}
foo().done(function(response) {
console.log(response);
});
Important rule
This is wrong:
function foo() {
let result;
setTimeout(function() {
result = 42;
}, 1000);
return result;
}
This is correct:
function foo() {
return new Promise(function(resolve) {
setTimeout(function() {
resolve(42);
}, 1000);
});
}
foo().then(function(result) {
console.log(result);
});
Step by Step Execution
Consider this code:
function foo() {
let result;
fetch('/api/data').then(function(response) {
result = response;
console.log('Inside then:', result);
});
console.log('Before return:', result);
return result;
}
const value = foo();
console.log('After calling foo:', value);
What happens step by step
foo()starts running.resultis declared but has the valueundefined.fetch('/api/data')starts the network request..then(...)registers a callback to run later when the request finishes.- JavaScript does not wait here.
console.log('Before return:', result)runs and printsundefined.return resultruns and returnsundefined.valuebecomesundefined.- Later, when the network request finishes, the
.then(...)callback runs. - Inside that callback,
result = responsehappens. - But
foo()already finished long ago, so changingresultnow does not change the old return value.
Likely output order
Before return: undefined
After calling foo: undefined
Inside then: Response {...}
Correct version with Promise
function foo() {
return fetch('/api/data').then(function(response) {
return response.json();
});
}
foo().then(function(data) {
console.log('Resolved data:', data);
});
Now the result is handled at the correct time: after the async work completes.
Real World Use Cases
Asynchronous results are used everywhere in JavaScript.
API calls
Frontend apps fetch data from servers:
async function loadUsers() {
const response = await fetch('/api/users');
return response.json();
}
Reading files in Node.js
Servers read configuration files, templates, or uploaded data:
const fs = require('fs');
fs.readFile('config.json', 'utf8', function(err, text) {
if (err) {
console.error(err);
return;
}
console.log(text);
});
Database queries
Applications wait for the database before continuing:
async function getUserById(id) {
return db.users.findById(id);
}
Delays and timers
setTimeout(function() {
console.log('Runs later');
}, 1000);
Form submission
A form is submitted, then success or error is shown after the server responds.
Background processing
Apps wait for uploads, report generation, payment confirmation, or email delivery.
Real Codebase Usage
In real projects, developers rarely try to force async code into synchronous style. Instead, they structure functions around async flow.
Common pattern: return a Promise
function getPosts() {
return fetch('/api/posts').then(function(response) {
if (!response.ok) {
throw new Error('Failed to load posts');
}
return response.json();
});
}
This lets callers decide how to handle the result:
getPosts()
.then(function(posts) {
console.log(posts);
})
.catch(function(error) {
console.error(error);
});
Common pattern: async function with validation
async function getUser(id) {
if (!id) {
throw new Error('id is required');
}
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error('User not found');
}
return response.json();
}
This uses:
- guard clauses for invalid input
- early error throwing
- returning a Promise implicitly
Common pattern: transforming async results
async function getUserNames() {
const users = await fetch('/api/users').then(function(r) {
return r.json();
});
return users.map(function(user) {
return user.name;
});
}
Common pattern: error handling with try/catch
async function loadData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Failed to load data:', error);
throw error;
}
}
Common pattern: parallel async work
async function loadEverything() {
const [users, posts] = await Promise.all([
fetch('/api/users').then(function(r) { return r.json(); }),
fetch('/api/posts').then(function(r) { return r.json(); })
]);
return { users, posts };
}
This is common when multiple independent requests can run at the same time.
Common Mistakes
1. Returning from inside the callback
Broken code:
function foo() {
fs.readFile('file.txt', 'utf8', function(err, data) {
return data;
});
}
Why it is wrong:
return datareturns from the callback function only- it does not return from
foo()
2. Returning a variable too early
Broken code:
function foo() {
let result;
setTimeout(function() {
result = 42;
}, 1000);
return result;
}
Why it is wrong:
return resultruns beforesetTimeoutfires
3. Forgetting to return the Promise
Broken code:
function foo() {
fetch('/api/data').then(function(response) {
return response.json();
});
}
Why it is wrong:
foo()returnsundefinedbecause the Promise is never returned
Correct version:
function foo() {
return fetch('/api/data').then(function(response) {
return response.json();
});
}
4. Forgetting to await
Broken code:
async function foo() {
const data = fetch('/api/data');
return data;
}
This returns a Promise for the response, not the parsed result.
Better:
async function foo() {
const response = await fetch('/api/data');
return response.json();
}
5. Ignoring errors
Broken code:
async function foo() {
const response = await fetch('/bad-url');
return response.json();
}
Better:
async function foo() {
try {
const response = await fetch('/bad-url');
return await response.json();
} catch (error) {
console.error(error);
throw error;
}
}
6. Mixing styles carelessly
You can mix callbacks, Promises, and async/await, but beginners often make code harder to read by combining them unnecessarily.
Prefer one clear style when possible:
- old APIs: callbacks
- modern APIs: Promises
- modern application code:
async/await
Comparisons
| Approach | How it works | Good for | Downsides |
|---|---|---|---|
| Callback | Pass a function to be called later | Older APIs, simple event-based work | Can become nested and hard to read |
| Promise | Return an object representing a future value | Chaining async operations | Requires understanding .then() and .catch() |
async/await | Write Promise-based code in a synchronous-looking style | Modern JavaScript application code | Still asynchronous; must be used with Promises |
Returning a value vs returning a Promise
| Pattern | What is returned | When value is available |
|---|---|---|
return 5 | Immediate value | Right now |
return fetch(...) | Promise | Later |
return await fetch(...) inside async function | Still a Promise from the function | Later |
.then() vs await
function getData() {
return fetch('/api/data').then(function(r) {
return r.json();
});
}
async function getData() {
const r = await fetch('/api/data');
return r.json();
}
Both are valid. await is usually easier to read.
Async vs synchronous thinking
| Synchronous | Asynchronous |
|---|---|
| Code waits for result | Code continues running |
| Return value is immediate | Result arrives later |
| Easier flow | Requires callbacks, Promises, or await |
Cheat Sheet
Core rule
You cannot return the final value from async code synchronously.
Wrong pattern
function foo() {
let result;
asyncOperation(function(data) {
result = data;
});
return result;
}
Correct patterns
Callback
function foo(callback) {
asyncOperation(function(data) {
callback(data);
});
}
Promise
function foo() {
return asyncOperationPromise();
}
Async/await
async function foo() {
const data = await asyncOperationPromise();
return data;
}
Important facts
returninside a callback does not return from the outer functionasync functionalways returns a Promise.then(...)runs laterawaitcan only be used insideasyncfunctions or supported module contexts- if you want callers to use the result later, return a Promise
Common fix
Instead of:
const value = foo();
Use:
foo().then(function(value) {
console.log(value);
});
Or:
const value = await foo();
FAQ
Why does my function return undefined after fetch?
Because fetch is asynchronous. Your function returns before the request finishes.
Can I force async code to return synchronously?
Usually no. The correct solution is to use callbacks, Promises, or async/await.
Does return inside .then() return from the outer function?
No. It returns from the .then() callback and affects the Promise chain, not the outer synchronous function.
What does an async function return?
It always returns a Promise.
Should I use callbacks or async/await?
For modern JavaScript, prefer Promises and async/await. Use callbacks mainly when working with older APIs.
Do I still need error handling with await?
Yes. Use try/catch or let the error propagate to the caller.
Is await synchronous?
No. It only makes asynchronous code look easier to read. The function still returns a Promise.
Why does setting a local variable inside a callback not help?
Because the callback runs later, after the outer function has already returned.
Mini Project
Description
Build a small JavaScript utility that loads JSON data from an API and prints selected information. This project demonstrates the correct way to return asynchronous results using Promises and async/await instead of trying to return values too early.
Goal
Create a function that fetches user data asynchronously and returns parsed JSON so another function can use it safely.
Requirements
- Create an async function that requests data from a URL.
- Check whether the HTTP response is successful.
- Parse the response as JSON.
- Return the parsed data from the async function.
- Call the function and print part of the result.
- Handle errors clearly.
Keep learning
Related questions
Abort Ajax Requests with jQuery jqXHR.abort()
Learn how to cancel an in-progress jQuery Ajax request with jqXHR.abort(), handle abort status safely, and avoid stale UI updates.
Access the Correct this Inside a JavaScript Callback
Learn why JavaScript this changes in callbacks and how to preserve an object context using bind, arrow functions, and event handler patterns.
Add Key-Value Pairs to JavaScript Objects
Learn how to add key-value pairs to JavaScript objects with dot and bracket notation, dynamic keys, examples, and common mistakes.