1

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
})
2
  • 1
    Your forEachSequential does not return anything, it only "consumes" all promises. You want to build up an array and put the results there... Commented Feb 1, 2022 at 8:22
  • @GACy20 I don't see where the return is missing... Commented Feb 1, 2022 at 8:23

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

3 Comments

(Oops, editing error -- if you see .call in the above, hit refresh.)
As it is a seqential mapping, is it possible to some progress information? As I said, I'm trying to handle some file uploads, which can of course be successful or failing. But I think it would be helpful if I can show the user 2 of 5 uploads are done. If I understand it correctly, the mapSequential just returns the total result at the end. So do i have a chance to get some infos out of the loop?
@user3142695 - The callback would be able to do that, since it's called on each iteration.
0

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];

Comments

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.