37

Typescript build is failing as it does not seem to like Promise.allSetttled even though I have set ts config comilerOptions with "lib": [ "ES2020.Promise" ],

It seems as though the response for promise.allSettled does not include result or reason.

When running typescript build I get the following error:

Property 'reason' does not exist on type 'PromiseSettledResult<IMyPromiseResult>'.

and

Property 'value' does not exist on type 'PromiseRejectedResult'.

My code block looks like this and as you can see, I am trying to access reason and result from eaech of the promises that get resolved.

const myPromise = async () : Promise<IMyPromiseResult> {
  return new Promise((resolve) => {
    resolve("hello world")
  })
}

const data = await Promise.allSettled([
  myPromise()
]);

const response = data.find(res => res.status === 'fulfilled')?.result;

if(!response) {
  const error = data.find(res => res.status === 'rejected')?.reason;
  throw new Error(error);
}

How can I update the Promise.allSettled declaration to include the correct interfaces?

Stretch0
  • 8,362
  • 13
  • 71
  • 133
  • 1
    From your result handling code it looks like what you're actually looking for is [`Promise.any`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any)? – Bergi Nov 20 '20 at 11:12
  • 1
    The problem would be that a `PromiseSettledResult` is a discriminated union, and although you are using `find` with the appropriate predicate, Typescript is not clever enough to cast the `find` result to the respective `PromiseFulfilledResult`/`PromiseRejectedResult`. – Bergi Nov 20 '20 at 11:14
  • @Bergi yes you are right that `Promise.any` would be better but unfortunately it is not available in the version of node I am using – Stretch0 Nov 20 '20 at 11:39

6 Answers6

38

Like bela53 stated, use type guards. A more elegant solution than to inline the type guards is to define them as separate functions, and with generics binding you'd get the correct value too for the fulfilled promises, and can reuse for any allSettled filtering needs.

Casting is not needed (and generally should be avoided).

const isRejected = (input: PromiseSettledResult<unknown>): input is PromiseRejectedResult => 
  input.status === 'rejected'

const isFulfilled = <T>(input: PromiseSettledResult<T>): input is PromiseFulfilledResult<T> => 
  input.status === 'fulfilled'

const myPromise = async () => Promise.resolve("hello world");

const data = await Promise.allSettled([myPromise()]);

const response = data.find(isFulfilled)?.value
const error = data.find(isRejected)?.reason
vesse
  • 4,871
  • 26
  • 35
21

Like Bergi mentioned TypeScript does not know if the type is PromiseFulfilledResult / PromiseRejectedResult when checking types.

The only way is to cast the promise result. This can be done because you already verified that the resolved promise is either fulfilled or rejected.

See this example:

const myPromise = async (): Promise<string> => {
  return new Promise((resolve) => {
    resolve("hello world");
  });
};

const data = await Promise.allSettled([myPromise()]);

const response = (data.find(
  (res) => res.status === "fulfilled"
) as PromiseFulfilledResult<string> | undefined)?.value;

if (!response) {
  const error = (data.find(
    (res) => res.status === "rejected"
  ) as PromiseRejectedResult | undefined)?.reason;
  throw new Error(error);
}
Jens
  • 440
  • 3
  • 5
18

Use a type guard:

const isFulfilled = <T,>(p:PromiseSettledResult<T>): p is PromiseFulfilledResult<T> => p.status === 'fulfilled';
const isRejected = <T,>(p:PromiseSettledResult<T>): p is PromiseRejectedResult => p.status === 'rejected';

const results = await Promise.allSettled(...);
const fulfilledValues = results.filter(isFulfilled).map(p => p.value);
const rejectedReasons = results.filter(isRejected).map(p => p.reason);
Greggz
  • 1,873
  • 1
  • 12
  • 31
Kabir Sarin
  • 18,092
  • 10
  • 50
  • 41
3

Define the find callback as type guard, that returns a type predicate:

type IMyPromiseResult = string

const response = data.find(
  (res): res is PromiseFulfilledResult<string> => res.status === 'fulfilled'
)?.value;

if (!response) {
  const error = data.find(
    (res): res is PromiseRejectedResult => res.status === 'rejected'
  )?.reason;
  throw new Error(error);
}

Demo Playground

bela53
  • 3,040
  • 12
  • 27
  • 1
    See also [TypeScript issue #16069](https://github.com/microsoft/TypeScript/issues/16069) – Bergi Nov 20 '20 at 13:21
3

This makes ts not angry.

const rawResponse = await Promise.allSettled(promiseArray);
const response = rawResponse.filter((res) => res.status === 'fulfilled') as PromiseFulfilledResult<any>[];

const result = response[0].value
xyh
  • 87
  • 3
1

A bit more context for handling arrays:

const fulfilled = (res.filter((r) => r.status === 'fulfilled') as PromiseFulfilledResult<any>[]).map(
  (r) => r.value
);
(res.filter((r) => r.status === 'rejected') as PromiseRejectedResult[]).forEach((r) =>
  console.warn(r.status, r.reason)
);