Question

How to Return Values from Asynchronous Functions in JavaScript

javascriptajaxasynchronous

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:

  • undefined values
  • 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:

  1. Place the order
  2. 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.

Take Quiz

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

  1. foo() starts running.
  2. result is declared but has the value undefined.
  3. fetch('/api/data') starts the network request.
  4. .then(...) registers a callback to run later when the request finishes.
  5. JavaScript does not wait here.
  6. console.log('Before return:', result) runs and prints undefined.
  7. return result runs and returns undefined.
  8. value becomes undefined.
  9. Later, when the network request finishes, the .then(...) callback runs.
  10. Inside that callback, result = response happens.
  11. But foo() already finished long ago, so changing result now 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.

Take Quiz

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 data returns 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 result runs before setTimeout fires

3. Forgetting to return the Promise

Broken code:

function foo() {
    fetch('/api/data').then(function(response) {
        return response.json();
    });
}

Why it is wrong:

  • foo() returns undefined because 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

ApproachHow it worksGood forDownsides
CallbackPass a function to be called laterOlder APIs, simple event-based workCan become nested and hard to read
PromiseReturn an object representing a future valueChaining async operationsRequires understanding .then() and .catch()
async/awaitWrite Promise-based code in a synchronous-looking styleModern JavaScript application codeStill asynchronous; must be used with Promises

Returning a value vs returning a Promise

PatternWhat is returnedWhen value is available
return 5Immediate valueRight now
return fetch(...)PromiseLater
return await fetch(...) inside async functionStill a Promise from the functionLater

.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

SynchronousAsynchronous
Code waits for resultCode continues running
Return value is immediateResult arrives later
Easier flowRequires callbacks, Promises, or await
Take Quiz

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

  • return inside a callback does not return from the outer function
  • async function always returns a Promise
  • .then(...) runs later
  • await can only be used inside async functions 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.

Take Quiz

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.
Take Quiz

Keep learning

Related questions

View more