Evanss
Evanss

Reputation: 23553

TypeScript function return type error when I move from await to Promise.all

I have a function. The return type is a promise of a User:

async function doThing(): Promise<User> {
  const one = await getOne();
  const two = await getTwo();
  return {
    one,
    two
  }
}

Im changing the function to use Promise.all:

async function doThing(): Promise<User> {
  const onePromise = getOne();
  const twoPromise = getTwo();

  await Promise.all([onePromise, twoPromise])
    .then(([one, two])=>{
      return {
        one,
        two
      }
    });
}

However now I get a TypeScript error:

TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.

Im surprised I'm getting an error in the 2nd but not 1st version of the code. I tried catching the error but it made no difference:

  await Promise.all([onePromise, twoPromise])
    .then(([one, two])=>{
      return {
        one,
        two
      }
    }).catch(error => {
       return Error(error);
    });

But throwing an error after Promise.all does make the error go away:

  await Promise.all([onePromise, twoPromise])
    .then(([one, two])=>{
      return {
        one,
        two
      }
    });
    throw new Error("oh no!")

Upvotes: 0

Views: 2517

Answers (1)

Cameron Bielstein
Cameron Bielstein

Reputation: 429

As @jcalz mentioned, you're missing a return statement in your code. The short answer is that the return statement inside the .then returns only for the function you defined in .then, not for the larger doThing function.

That's a bit confusing in this scenario with await, Promise.all, and Promise.then all in play, so we can break it down in depth. It helps me to isolate each item in the chain of methods and look at what they actually return.

But first, let's refresh on what each of these things do:

  • Promise.all combines the list of promises (Promise<any>[]) to a promise of a list (Promise<any[]>). This is convenient and allows us to wait on only a single promise to get all of our values.

  • Promise.then allows us to act on a Promise once it is complete (similar to await) and in this case to convert that Promise to a different type. This is done by defining a function that accepts the input value and returns an a new type of value. It's important to note that a return statement inside of that function determines the return value for .then, not the function calling it.

  • await: Await unwraps a value from a Promise. It also blocks execution of the function while it waits.

So let's break that chain up and add explicit types to follow it through:

async function doThing(): Promise<User> {

  // Promises are created for whatever type getOne and getTwo return, let's say it's any
  const onePromise: Promise<any> = getOne();
  const twoPromise: Promise<any> = getTwo();

  // Promise.all converts a list of Promises (Promise<any>[]) in to a promise of a list (Promise<any[]>)
  const allPromises: Promise<any[]> = Promise.all([onePromise, twoPromise]);

  // .then convers the Promise of a list (Promise<any[]>) in to a promise of a user (Promise<User>)
  // The function inside takes in the unwrapped any[] and returns whatever we want wrapped in a new promise (User, in this case)
  // Note that the return here is determining the return value for `.then`, NOT for `doThing` because it is nested inside a separate function.
  // `return` statements are subject to scoping rules similar to variable scope
  const userPromise: Promise<User> = allPromises.then((values: any[]) => return { values[0], values[1] });

  // await unwraps the promised user (Promise<User>) to just a User
  const user: User = await userPromise;

  // We can now return, this is the line that was missing from your original code.
  return user;
}

So the await Promise.all(...).then(...) chain you have results in a User object...but without a return or assignment to a variable, your function does nothing with it. Your code just needs to add the return to the front of that chain.

As a note, this could be done more simply, in my opinion, without the call to .then and instead awaiting the Promise.all

async function doThing(): Promise<User> {
  const onePromise: Promise<any> = getOne();
  const twoPromise: Promise<any> = getTwo();

  // Promise.all returns Promise<any[]> and awaiting returns any[]
  const values: any[] = await Promise.all([onePromise, twoPromise]);

  // Now we can return directly from here
  return { values[0], values[1] };
}

Separately, why did throwing a new error stop the TypeScript error? The TypeScript error was generated by the TypeScript compiler when it detected a method that would execute to the end without returning a value. By throwing an error in the function, you ensured that the interpreter will never reach the end of the function without something to return...because it will never reach the end of the function. So the code is "correct" even if it isn't functional.

Upvotes: 2

Related Questions