DEV Community

Cathy Lai
Cathy Lai

Posted on

Understanding Async Functions: Why You Don't Need `Promise.resolve()` in `async` Functions

Introduction

If you've worked with async/await in JavaScript or TypeScript, you might have encountered a common question: Why can you return a plain value from an async function even though the return type is Promise<T>?

This is a great question that trips up many developers! Let me explain how async functions automatically handle return values.

The Question

Why can you write this:

const syncToServer = async (): Promise<{ success: boolean }> => {
    if (itemsToSync.length === 0) {
        return { success: true };  // ← Just a plain object, not a Promise!
    }
    // ...
}
Enter fullscreen mode Exit fullscreen mode

Instead of this:

const syncToServer = async (): Promise<{ success: boolean }> => {
    if (itemsToSync.length === 0) {
        return Promise.resolve({ success: true });  // ← Wrapped in Promise
    }
    // ...
}
Enter fullscreen mode Exit fullscreen mode

The Answer: async Functions Auto-Wrap Return Values

When you declare a function with async, JavaScript automatically wraps any return value in a Promise. You don't need to manually create a Promise.

How It Works

When you write:

const myFunction = async () => {
    return { data: "hello" };
}
Enter fullscreen mode Exit fullscreen mode

JavaScript automatically converts it to:

const myFunction = () => {
    return Promise.resolve({ data: "hello" });
}
Enter fullscreen mode Exit fullscreen mode

Code Examples

Example 1: Simple Return Value

// ✅ This works perfectly
const fetchData = async (): Promise<string> => {
    return "Hello, World!";  // Automatically wrapped in Promise.resolve()
}

// Usage
fetchData().then(result => console.log(result)); // "Hello, World!"
Enter fullscreen mode Exit fullscreen mode

Example 2: Conditional Returns

const syncToServer = async (): Promise<{ success: boolean; errorMsg?: string }> => {
    const itemsToSync = getUnsyncedItems();

    if (itemsToSync.length === 0) {
        return { success: true };  // ✅ Auto-wrapped in Promise
    }

    try {
        await sendToServer(itemsToSync);
        return { success: true };  // ✅ Also auto-wrapped
    } catch (error) {
        return { success: false, errorMsg: error.message };  // ✅ Also auto-wrapped
    }
}
Enter fullscreen mode Exit fullscreen mode

Example 3: All These Are Equivalent

// Option 1: Using async (RECOMMENDED - cleanest)
const getValue = async (): Promise<number> => {
    return 42;
}

// Option 2: Manually wrapping (works, but redundant)
const getValue = async (): Promise<number> => {
    return Promise.resolve(42);  // Unnecessary when using async
}

// Option 3: Without async, must manually create Promise
const getValue = (): Promise<number> => {
    return Promise.resolve(42);  // Required if not using async
}
Enter fullscreen mode Exit fullscreen mode

Example 4: Error Handling

const fetchUser = async (id: string): Promise<User> => {
    if (!id) {
        throw new Error("ID is required");  // ✅ Auto-wrapped in Promise.reject()
    }

    const user = await database.getUser(id);
    return user;  // ✅ Auto-wrapped in Promise.resolve()
}

// Usage
fetchUser("123")
    .then(user => console.log(user))
    .catch(error => console.error(error));  // Handles the thrown error
Enter fullscreen mode Exit fullscreen mode

Key Rules

  1. async functions always return a Promise - even if you return a plain value
  2. Returning a value = automatically wrapped in Promise.resolve(value)
  3. Throwing an error = automatically wrapped in Promise.reject(error)
  4. You can still return a Promise explicitly, but it's unnecessary

When You DO Need new Promise()

You only need new Promise() when you're wrapping non-promise-based async code (like callbacks):

// Wrapping a callback-based API
const delay = (ms: number): Promise<void> => {
    return new Promise((resolve) => {
        setTimeout(() => resolve(), ms);  // Callback → Promise
    });
}

// But with async/await, you can simplify:
const delay = async (ms: number): Promise<void> => {
    await new Promise(resolve => setTimeout(resolve, ms));
}
Enter fullscreen mode Exit fullscreen mode

Tags: #javascript #typescript #async-await #promises #webdev #programming

Top comments (0)