Reputation: 23553
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
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