Typescript Function returns before promise.all finishes

260 Views Asked by At

I have a code as follow

export class MyHandler {
    entry = async (data: string[]): Promise<Map<string, string>> {
        const response: Map<string, string> = new Map();
        Promise.all(
            data.map(async (item) => {
                const apiGetDataRequest = {
                    data: item
                };
                const apiGetDataResponse = await this.client.apiCall(apiGetDataRequest);
                return apiGetDataResponse.data;
            });
        ).then((results) => {
            for (const result of results) {
                const value = myFirstMethod([1, 2, 3]);
                response.set(result, value);
            }
        });

        return response;
    };

    myFirstMethod = (items: number[]): string {
        const result = mySecondMethod(items, 'Test');
        console.log(result);
        return result;
    };

    mySecondFunction = (items: number[]): string {
        let finalResult = "";
        Promise.all(
            items.map(async (item) => {
                const apiCallRequest = {
                    data: item
                };
                const apiCallResponse = await this.client.apiCall(apiCallRequest);
                return apiCallResponse.data;
            });
        ).then((results) => {
            for (const result of results) {
                finalResult = finalResult + ', ' + result;
            }
        });

        return finalResult;
    };
}

The issue that I'm running into is that mySecondFunction returns before all promises are done and as the result, result in myFirstMethod always has a empty string value.

How can I have mySecondFunction to wait for all promises before returning?

2

There are 2 best solutions below

6
Matthieu Riegler On

Your mySecondFunction method is synchronous, you will need to await on Promise.all.

mySecondFunction = async (items: number[]): string {
    let finalResult = "";
    await Promise.all(
        items.map(async (item) => {
            const apiCallRequest = {
                data: item
            };
            const apiCallResponse = await this.client.apiCall(apiCallRequest);
            return apiCallResponse.data;
        });
    ).then((results) => {
        for (const result of results) {
            finalResult = finalResult + ', ' + result;
        }
    });

    return finalResult;
};
0
Jeff Bowman On

Though Matthieu's answer is correct, you can also make this same upgrade to mySecondFunction without using async. This is because async is just syntactic sugar for a function that returns a Promise. That would look like this:

// not async
mySecondFunction = (items: number[]): string {
    // Return the chained promise.
    return Promise.all(
        items.map(async (item) => {
            const apiCallRequest = {
                data: item
            };
            const apiCallResponse = await this.client.apiCall(apiCallRequest);
            return apiCallResponse.data;
        })
    ).then((results) => {
        // Style nit: Move the definition of finalResult closer to its use.
        let finalResult = "";
        for (const result of results) {
            finalResult = finalResult + ', ' + result;
        }
        // Return finalResult within the promise chain, so this becomes
        // the result of the Promise the function returns.
        return finalResult;
    });
};

If you're in an environment that supports async/await—and you are, because you're doing so within map—then there's not much reason to prefer this over the other style. That said, much of the value of async is to avoid explicit promise chains with then, and you still have one in your code. You may find your code easier to read if you avoid mixing styles.

mySecondFunction = async (items: number[]): string {
    let finalResult = "";
    let results = await Promise.all(
        items.map(async (item) => {
            const apiCallRequest = {
                data: item
            };
            const apiCallResponse = await this.client.apiCall(apiCallRequest);
            return apiCallResponse.data;
        })
    );

    // Instead of putting this in a `then` method,
    // just use the results you've awaited above.
    for (const result of results) {
        finalResult = finalResult + ', ' + result;
    }

    return finalResult;
};

(With either style, you'll need to change myFirstMethod to await the result of mySecondFunction and also change your entry function to ensure it is awaiting the result of myFirstMethod. Such is the nature of asynchronous code: you have to wait for everything you depend on.)