Home > front end >  Calling Promise.all() executes correctly but calling await on individual promises does not
Calling Promise.all() executes correctly but calling await on individual promises does not

Time:12-17

I have some code that iterates over a list of objects representing files. In the loop there is a call to 'getRequest()' which returns type 'Promise<Response>' and then a call to '.text()' which returns 'Promise<string>'. I have a working solution that first gets all the promises into a list and then calls Promise.all() and the documents are fetched correctly.

However when I try another solution where I am directly waiting for the promise to return and then using the values, the documents are not populated correctly.

//----------------------------- WORKING SOLUTION ---------------------------------------
async function parseDocumentation(config: Map<string, ConfigElement[]>) {
    const promises: Promise<Response>[] = [];
    const documents: Documentation[] = [];

    config.forEach((sectionDef: ConfigElement[]) => {
        sectionDef.forEach(entry => {
            if (!entry.subsection) {
                // getRequest: (endpointUrl: string) => Promise<Response>
                promises.push(getRequest(entry.source));
            }
        });
    });
    try {
        await Promise.all(promises).then(async responses => {
            for (const response of responses) {
                if (!response.ok) {
                    return Promise.reject();
                } else {
                    const document = {source: '', markDown: '' };
                    document.markDown = await entry.text(); // Body.text(): Promise<string>
                    document.source = response.url;
                    documents.push(document);
                }
            }
        });
    } catch (e) {
        Dialog.error('Failed to load page.', e);
    }
}

//----------------------------- NON WORKING SOLUTION ---------------------------------------
async function parseDocumentation(config: Map<string, ConfigElement[]>) {
    const documents: Documentation[] = [];

    config.forEach((sectionDef: ConfigElement[], sectionName: string) => {
        sectionDef.forEach(async entry => {
            if (!entry.subsection) {
                entry.hash = 'some_hash_value_'   sectionName;
                await getRequest(entry.source).then(async response => {
                    if (!response.ok) {
                        return Promise.reject();
                    } else {
                        const document = {source: '', markDown: '' };
                        await response.text().then(async text => {
                            // would like to save entry.hash to document here
                            document.markDown = text;
                            document.source = response.url;
                            documents.push(document);
                        });
                    }
                });
            }
        });
    });
}

Can anyone explain what is happening? I think I mirrored the logic correctly but for some reason the second variation does not populate documents correctly.

CodePudding user response:

The problem is with .forEach(async .... construct: the callback is an asynchronous function, and the first await in it will return/suspend that callback function (not the outer function), making the forEach loop continue with the next iterations. The asynchronous code (below await) in the callbacks will only execute when the callstack is empty, so the forEach will already have finished its iterations before that happens.

To solve this, use a loop construct that doesn't need a callback, like for..of. Then the awaits operate on the parseDocumentation function itself:

async function parseDocumentation(config: Map<string, ConfigElement[]>) {
    const documents: Documentation[] = [];

    for (const [sectionName: string, sectionDef: ConfigElement[]] of config) {
        for (const entry: ConfigElement of sectionDef) {
            // ...
        }
    }
}

Not the question, but try to get the most out of await by getting the promised value from it -- avoiding a then chain:

async function parseDocumentation(config: Map<string, ConfigElement[]>) {
    const documents: Documentation[] = [];

    for (const [sectionName: string, sectionDef: ConfigElement[]] of config) {
        for (const entry: ConfigElement of sectionDef) {
            if (entry.subsection) continue;
            entry.hash = 'some_hash_value_'   sectionName;
            const response = await getRequest(entry.source);
            if (!response.ok) throw new Error("One of the responses was not OK");
            documents.push({
                source: response.url,
                markDown: await response.text(),
            });
        }
    }
}

Note that throwing an error in an async function will reject the function's returned promise with as reason the error message.

CodePudding user response:

You should do :

let text = await response.text();

  • Related