Skip to main content

How to Wait for a Promise to Resolve Before Returning in JavaScript

A fundamental concept in asynchronous JavaScript is ensuring that a function waits for a Promise to complete before it returns a value that depends on that promise. If a function returns too early, it will return a pending Promise or undefined, not the final data you need.

This guide will explain this core concept and demonstrate the two modern patterns for correctly handling this: using the async/await syntax (recommended) and the traditional .then() method.

The Core Problem: Asynchronous Operations

When you call a function that returns a Promise (like fetch() or a database query), that operation runs in the background. Your code does not stop and wait for it to finish.

Problem: in this example, the fetchData function returns immediately, before the fetch operation has completed.

// Problem: This function returns a pending Promise, not the user data.
function fetchData() {
const userPromise = fetch('https://api.example.com/user');
// The function returns right away, without waiting for the fetch to finish.
return userPromise;
}

const data = fetchData();
console.log(data); // # Output: Promise { <pending> }
note

To get the actual data, you must explicitly wait for the Promise to resolve.

The async/await syntax is the modern, standard, and most readable way to work with promises. It allows you to write asynchronous code that looks and behaves like synchronous code.

  • async: A keyword that marks a function as asynchronous. It ensures the function always returns a Promise.
  • await: A keyword that can only be used inside an async function. It pauses the function's execution until the Promise it is waiting on is resolved, and then unwraps the resolved value.

Solution:

// 1. Mark the function as `async`.
async function fetchData() {
console.log('Fetching data...');
const response = await fetch('https://api.example.com/user');

// 2. `await` the response to be parsed. The function pauses here.
const user = await response.json();

console.log('Data fetched:', user);

// 3. The function will now return the final `user` object.
return user;
}

// To use the function, you must also wait for its result.
async function main() {
const userData = await fetchData();
console.log('Final result in main:', userData);
}

main();
note

This is the recommended best practice for its clarity and simplicity.

Solution 2: The .then() Method

The .then() method is the classic way to handle promises. It allows you to chain callbacks that will execute when the promise is fulfilled.

Solution: to wait for a promise before returning, you must return the result of the .then() chain.

function fetchData() {
console.log('Fetching data...');

// Return the entire promise chain.
return fetch('https://api.example.com/user')
.then(response => {
// The first .then handles the HTTP response.
return response.json();
})
.then(user => {
// The second .then receives the parsed JSON data.
console.log('Data fetched:', user);
return user; // This is the final value the promise will resolve with.
});
}

// To use the function, you chain another .then() call.
fetchData().then(userData => {
console.log('Final result:', userData);
});
note

While this works perfectly, the async/await syntax is generally considered easier to read and debug.

How async Functions Work

An important concept is that an async function always returns a Promise.

  • If you explicitly return a value (like the user object in our example), the async function will wrap that value in a resolved Promise.
  • If you throw an error, the async function will return a rejected Promise.

This is why you must use await or .then() when you call an async function.

Handling Rejected Promises

When a promise is rejected, you must have code to "catch" the error.

  • With async/await, you use a try...catch block.
  • With .then(), you chain a .catch() block.
async function fetchData() {
try {
const response = await fetch('https://api.example.com/invalid-url');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Failed to fetch data:', error);
throw error; // Re-throw the error if you want the caller to handle it too.
}
}

The .catch() Solution

function fetchData() {
return fetch(...)
.then(...)
.catch(error => {
console.error('Failed to fetch data:', error);
throw error; // Re-throw
});
}

Conclusion

To wait for a Promise to resolve before returning from a function, you must use one of the two standard asynchronous patterns.

  • The async/await syntax is the recommended best practice. It is modern, highly readable, and makes error handling with try...catch very intuitive.
  • The .then() and .catch() methods are the classic alternative. They are still perfectly valid but can lead to more nested and less readable code (often called "callback hell").