Fixing Unexpected Await Errors In TypeScript

Alex Johnson
-
Fixing Unexpected Await Errors In TypeScript

Hey guys! Ever run into that head-scratching TypeScript error: "Unexpected await of a non-Promise (non-"Thenable") value"? It's like TypeScript's way of saying, "Hey, I'm expecting a promise here, but I'm not seeing one!" Let's dive into what causes this, how to fix it, and some best practices to avoid it in your code.

What Causes This Error?

The core issue behind the "Unexpected await of a non-Promise" error is that the await keyword in JavaScript (and TypeScript) is designed to work specifically with Promises. A Promise represents a value that might not be available yet but will be resolved at some point in the future. When you await a Promise, you're telling the code to pause execution until that Promise either resolves (succeeds) or rejects (fails).

So, what happens when you await something that isn't a Promise? Well, that's when TypeScript throws this error. It means you're trying to pause execution for something that doesn't have the asynchronous behavior of a Promise. This can happen in a few common scenarios:

  1. Awaiting a Non-Asynchronous Value: This is the most straightforward case. You might accidentally await a regular variable, a function that returns a plain value (like a number or a string), or some other non-Promise entity. For example:

    async function processValue(value: number) {
      const result = await value; // Error: value is not a Promise
      return result + 1;
    }
    

    In this case, value is just a number, not a Promise, so await doesn't make sense.

  2. Incorrectly Assuming a Function Returns a Promise: Sometimes, you might think a function returns a Promise when it actually doesn't. This can happen if you're working with a library or API that you're not entirely familiar with, or if you've made a mistake in your own code.

    function fetchUserData(userId: string): User {
      // This function synchronously fetches user data (not a good practice for real-world APIs!)
      return { id: userId, name: "John Doe" };
    }
    
    async function displayUserData(userId: string) {
      const user = await fetchUserData(userId); // Error: fetchUserData does not return a Promise
      console.log(user.name);
    }
    

    Here, fetchUserData returns a plain User object, not a Promise, so await is incorrect.

  3. Missing async Keyword: If you're calling an async function from within another function, and you forget to use the await keyword, you might end up with a Promise where you expect a value. However, the opposite can also happen: you might use await inside a function that isn't declared as async. In this case, TypeScript will also complain.

    function fetchData() {
      return Promise.resolve("Data fetched!");
    }
    
    async function processData() {
      const data = await fetchData();
      console.log(data);
    }
    
    processData();
    

How to Fix It? (Step-by-Step)

Okay, so you've got this error staring you in the face. What do you do? Here's a step-by-step approach to fixing it:

  1. Identify the Line: The TypeScript compiler will tell you exactly which line is causing the problem. Look closely at that line and the code around it.

  2. Determine the Type of the Awaited Value: Use TypeScript's type information to figure out what type of value you're actually awaiting. You can hover over the variable or function call in your editor to see its type. Is it a Promise<something>? If not, that's your problem.

  3. If It Should Be a Promise:

    • Ensure the Function Returns a Promise: If you expect a function to return a Promise, double-check its implementation to make sure it actually does. If it's a function you control, modify it to return a Promise. If it's from a library, consult the documentation to see how to use it correctly.

    • Wrap Non-Promise Values in Promise.resolve(): If you have a non-Promise value that you need to treat as a Promise, you can use Promise.resolve() to wrap it.

      async function processValue(value: number) {
        const promiseValue = Promise.resolve(value);
        const result = await promiseValue;
        return result + 1;
      }
      

      This tells JavaScript to treat the value as a Promise that immediately resolves to that value.

  4. If It Shouldn't Be a Promise:

    • Remove await: If the value you're awaiting is not supposed to be a Promise, simply remove the await keyword. This means you'll be working with the value directly, instead of waiting for it to resolve.

      function fetchUserData(userId: string): User {
        return { id: userId, name: "John Doe" };
      }
      
      async function displayUserData(userId: string) {
        const user = fetchUserData(userId); // Removed await
        console.log(user.name);
      }
      
  5. Check for Missing async: Ensure that the function containing the await keyword is actually declared as async. If not, add the async keyword to the function definition.

    async function processData() { // Added async
      const data = await fetchData();
      console.log(data);
    }
    

Practical Examples and Scenarios

Let's look at some real-world scenarios where this error might pop up and how to tackle them:

Scenario 1: Working with APIs

When fetching data from an API, you typically use fetch or a library like axios. These functions return Promises. However, it's easy to make mistakes, especially when handling errors.

async function fetchData(url: string) {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    const data = await response.json();
    return data;
  } catch (error) {
    console.error("Error fetching data:", error);
    return null; // or throw the error, depending on your needs
  }
}

async function processData() {
  const data = await fetchData("https://api.example.com/data");
  if (data) {
    console.log("Data received:", data);
  } else {
    console.log("Failed to fetch data.");
  }
}

In this example, fetch returns a Promise, and response.json() also returns a Promise. We correctly await both of them. If we were to accidentally remove the await from response.json(), we'd get a Promise instead of the actual data, which could lead to errors later on.

Scenario 2: Using setTimeout or setInterval

setTimeout and setInterval are often used for asynchronous operations, but they don't return Promises. If you want to use them with await, you need to wrap them in a Promise.

function delay(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function doSomething() {
  console.log("Starting...");
  await delay(2000); // Wait for 2 seconds
  console.log("Done!");
}

Here, we've created a delay function that returns a Promise that resolves after a specified number of milliseconds. This allows us to use await to pause execution until the timer completes.

Best Practices to Avoid This Error

Prevention is better than cure, right? Here are some best practices to help you avoid the "Unexpected await" error in the first place:

  1. Understand Asynchronous Code: Make sure you have a solid understanding of how asynchronous code works in JavaScript and TypeScript. Know the difference between synchronous and asynchronous operations, and how Promises are used to handle asynchronous tasks.

  2. Use TypeScript's Type System: TypeScript's type system is your friend! Use it to define the types of your variables and function return values. This will help you catch errors early on, including cases where you're awaiting the wrong type of value.

  3. Be Mindful of Function Return Types: Pay close attention to the return types of the functions you're calling, especially when working with libraries or APIs. Make sure you know whether a function returns a Promise or a plain value.

  4. Use a Linter: Linters like ESLint can help you catch common mistakes in your code, including incorrect use of await. Configure your linter to enforce best practices for asynchronous code.

  5. Write Unit Tests: Unit tests are a great way to verify that your asynchronous code is working correctly. Write tests that specifically check the behavior of your functions when they're dealing with Promises.

Conclusion

The "Unexpected await of a non-Promise" error can be a bit annoying, but it's usually easy to fix once you understand what's going on. By following the steps outlined in this article and adopting the best practices, you can avoid this error and write cleaner, more robust asynchronous code. Keep coding, and happy debugging!

For more information about asynchronous functions, check out the MDN Web Docs on async and await.

You may also like