user3142695
user3142695

Reputation: 17352

How to return value in sequential forEach loop?

This is how I'm using a sequential forEach loop to handle some file uploads. Now I want to get a total result for each upload: For each file I resolve with an object (success or error). At the end I want to get a returned array to display the result (File 1 successful, file 2 error and so on).

But with this code I only get undefined for the last output. What am I missing?

Maybe it would even be better to return two string arrays: one successful and one failed array with filenames.

Array.prototype.forEachSequential = async function (
    func: (item: any) => Promise<void>
): Promise<void> {
    for (let item of this) await func(item)
}
async uploadFiles(files: [File]): Promise<any> {
    const result = await files.forEachSequential(
        async (file): Promise<any> => {
        const res = await new Promise(async (resolve, reject) => {
            // do some stuff
            resolve({ success: file.filename })
            // or maybe
            resolve({ error: file.filename })
        })
        console.log(res) // returns object as expected
        return res
        }
    )
    // Get all results after running forEachSequential
    console.log('result', result) // result: undefined
})

Upvotes: 1

Views: 282

Answers (2)

T.J. Crowder
T.J. Crowder

Reputation: 1074919

Your forEachSequential function has no return statement, so it never returns anything, so calling it will always result in undefined (in this specific case, a promise that's fulfilled with undefined, since it's an async function). It completely ignores the return value of the callback.

You probably want a mapSequential (or mapSerial) instead, that builds an array of the results:

Object.defineProperty(Array.prototype, "mapSequential", {
    async value(func) {
        const result = [];
        for (const item of this) {
            result.push(await func(item));
        }
        return result;
    },
    writable: true,
    configurable: true,
});

Note: I recommend you don't add to Array.prototype. Consider doing this instead:

async function mapSequential(iterable, func) {
    const result = [];
    for (const item of iterable) {
        result.push(await func(item));
    }
    return result;
}

But if you do add to Array.prototype, be sure to do it with defineProperty as I did above, not just with assignment, and make sure the property you add isn't enumerable (either use enumerable: false or leave it off, false is the default).

You can spin this a lot of ways. You might consider a variant similar to Promise.allSettled that catches errors and returns an array indicating success/failure (with or without stopping early on first error). For instance:

async function mapSequential(iterable, func, options) {
    const result = [];
    for (const item of iterable) {
        try {
            const value = await func(item);
            result.push({ status: "fulfilled", value });
        } catch (reason) {
            result.push({ success: "rejected", reason });
            if (options?.shortCircuit) { // Option that says "stop at first error"
                break;
            }
        }
    }
    return result;
}

Again, though, you can spin it lots of ways. Maybe add an index and the iterable to the callback like map does, perhaps have an optional thisArg (although arrow functions make that largely unnecessary these days), etc.

Upvotes: 2

Aitwar
Aitwar

Reputation: 903

Your forEachSequential doesn't really return anything. That's why you get undefined. When awaiting, you should attach a result to some variable, store it somewhere and then at the end of this function you should return it.

Now, you would like to return a tuple [string[], string[]]. To do so you have to create to arrays at the beginning of forEachSequential body, then call func in the loop and add a result to first array (let's say - success array) or second (failure array).

const successArray = [];
const failureArray = [];

for (let item of this) {
    const result = await func(item);

    if ('success' in result) {
        successArray.push(result.success);
    } else {
        failureArray.push(result.error);
    }
}

return [successArray, failureArray];

Upvotes: 0

Related Questions