In JavaScript, nested promises will automatically be flattened by .then
and await
: await Promise.resolve(Promise.resolve(5)) === 5
. This will cause issues with asynchronous methods taking generic values; as they will behave different if they take a Promise than with any other object.
For example, take the following method:
1
2
3
async function identity(t) {
return t;
}
With this method, we would expect the following assertion to always be truthy:
1
alert(obj === await identity(obj));
And that is the case… unless obj
is a Promise. This means that generic asynchronous data structures are unable to deal with Promises, and TypeScript sometimes fails to be typesafe:
1
2
3
4
5
async function identity<T>(t: T): Promise<T> {
return t;
}
identity(Promise.resolve(5)).then((val: number) => alert(val))
Even if the val
is a number because .then
flattens the Promise, TypeScript does not recognize it as such:
1
2
3
Argument of type '(val: number) => void' is not assignable to parameter of type '(value: Promise<number>) => void | PromiseLike<void>'.
Types of parameters 'val' and 'value' are incompatible.
Type 'Promise<number>' is not assignable to type 'number'.
This design decision was likely made so we only need .then
(takes object or Promise) and not .then
(takes object) and .flatThen
(takes Promise). Which is an understandable position for convenience, however there are better alternatives; if the callback of .then
were to expect Promise<T>
, but simply wrap any values that are not in a Promise into one, we wouldn’t get full type safety, but we’d get the convenience of a single .then
method, but still allow us to create asynchronous data structure that may contain any objects, including Promises.
1
2
3
4
5
assert(await 5 === 5); // always succeeds
assert(await Promise.resolve(5) === 5); // always succeeds
assert(await Promise.resolve(Promise.resolve(5)) === 5); // succeeds with JavaScript's Promises, fails in ours
assert(await someObj === someObj); // fails in both definitions if someObj is a Promise; no full type safety
assert(await Promise.resolve(someObj) === someObj); // succeeds with our Promises, fails in JavaScript's if someObj is a Promise
With this alternative Promise definition, languages like TypeScript could get around type errors by simply disallowing (as a warning or compiler flag) await
ing anything that’s not a guaranteed Promise at compile-time.