0

I've been diving deep into TypeScript recently and encountered a challenge while working with Promise.allSettled. My goal is to fetch various pieces of weather data concurrently (like hourly forecast, daily forecast, air pollution, UV index, and current weather conditions). After fetching, I'm processing these promises within a loop, specifically in a reduce function, to organize the data into a structured format.

public async fetchAllData(): Promise<FetchResult> {
      try {
        const results = await Promise.allSettled([
          this.getHourlyForecast(23),
          this.getDailyForecast(10),
          this.getAirPollution(),
          this.getUvIndexForecast,
          this.getCurrentWeather
        ]);
        const promiseNames:(keyof AccType)[] = [
          'hourlyData', 
          'tenDayForecast', 
          'airPollution', 
          'uvIndex', 
          'currentWeather'
        ];
        const data = promiseNames.reduce((
          acc: AccType, 
          name: keyof AccType, 
          index: number) => {
          if (results[index].status === 'fulfilled') {
            acc[name] = (results[index]).value   // error is here
          } else {
            acc[name] = undefined;
          }
          return acc;
        }, {});
        console.log(JSON.stringify(data.airPollution, null, 2));
        return data;
      } catch (error: any) {
        return { error: error.message };
      }
    }

​ here is the error :

[{ "resource": "/C:/Users/Shiha/Desktop/resume/app/(weather)/class.tsx", "owner": "typescript", "code": "2339", "severity": 8, "message": "Property 'value' does not exist on type 'PromiseRejectedResult | PromiseFulfilledResult | PromiseFulfilledResult | PromiseFulfilledResult<...> | PromiseFulfilledResult<...> | PromiseFulfilledResult<...>'.\n Property 'value' does not exist on type 'PromiseRejectedResult'.", "source": "ts", "startLineNumber": 95, "startColumn": 42, "endLineNumber": 95, "endColumn": 47 }]

The crux of my issue lies in typing the results of Promise.allSettled within the reduce function. As you can see, I'm currently using (results[index] as PromiseFulfilledResult<any>).value to extract the value of fulfilled promises, but this uses any, which I'm trying to avoid to leverage TypeScript's type safety features fully.

I'm looking for 2 things:

  • A way to dynamically assign types to the results based on the promise it corresponds to, thus avoiding the use of any.

  • How to handle this within a loop, especially using the reduce function, in a way that TypeScript can understand and enforce the types of each result correctly.

I've met this problem so many times, but every time I just use any to skip it. I'm looking for a better way to handle this.

3
  • There's nothing much better, I'm afraid, see this question and answer. Does that fully address your question or am I missing something? Commented Feb 9, 2024 at 18:34
  • What is the ultimate goal here? Do you want to have a result with all promises resolved and mapped to your output object? Or do you want a result which only has the values of resolved promises? Commented Feb 10, 2024 at 14:56
  • The below answer from @Shane fully solves the problem. Commented Feb 11, 2024 at 20:01

1 Answer 1

0

Here are 2 solutions to your issue.

  1. is elegant and uses some advanced typescript features.
  2. is more bare bones and readable

First, some code shared by both solutions:

type AccType = {
    uvIndex: number | undefined
    currentWeather: string | undefined
}

function getUvIndexForecast(): Promise<number> {
    return Promise.resolve(123)
}

function getCurrentWeather(): Promise<string> {
    return Promise.resolve("weather")
}

Solution 1

type KeyValuePair<T> = { 
    [K in keyof T]-?: { key: K, value: T[K] } 
}[keyof T]

async function fetchAllData(): Promise<AccType> {
    const req: Promise<KeyValuePair<AccType>>[] = [
        getUvIndexForecast().then(value => ({key: "uvIndex", value})),
        getCurrentWeather().then(value => ({key: "currentWeather", value}))
    ]

    const response = await Promise.allSettled(req)
    return response
        .reduce((s, x) => x.status === "fulfilled"
            ? { ...s, [x.value.key]: x.value.value } 
            : s, {} as AccType)
}

inspired by: TypeScript type alias for key-value pair

Solution 2

function rejectedAsUndefined<T>(promise: Promise<T>): Promise<T | undefined> {
    return promise.catch(_ => undefined)
}

async function fetchAllData(): Promise<AccType> {
    const uvIndex = rejectedAsUndefined(getUvIndexForecast())
    const currentWeather = rejectedAsUndefined(getCurrentWeather())

    return {
        uvIndex: await uvIndex,
        currentWeather: await currentWeather
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

both solution all work, you are amazing bro. use "[keyof T]" as union type and "-?" explicitly declare the type are amazing

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.