Reputation: 17352
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
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
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