The problem is that you are using async
and await
in the forEach
call, and this doesn't work as you would expect.
The forEach
method doesn't really care about the return value of the callback function (in this case the promise that getStats
returns).
You should map
the things
array to promises, and use Promise.all
:
async function getThings (folder) {
const things = await fs.promises.readdir(folder);
const promises = things.map(thing => getStats(thing, folder));
return await Promise.all(promises);
}
Note that this will execute the promises "in parallel", and not sequentially.
If you want to execute the promises secuentially, one by one, you can either, "reduce" the promise array, or use a conventional loop (for
, for-of
).
EDIT:
Let me try to clarify why using an async callback function with forEach doesn't work.
Attempt #1: De-sugarizing it:
In the original example we have something like this:
// ...
things.forEach(async (thing)=>{
await getStats(thing, folder);
});
// ...
If we separate the callback from the forEach
, we have:
const callback = async (thing)=>{
await getStats(thing, folder);
};
things.forEach(callback);
If we "desugarize" the async function:
const callback = function (thing) {
return new Promise((resolve, reject) => {
try {
getStats(thing, folder).then(stat => /*do nothing*/);
} catch (err) {
reject(err);
}
resolve();
});
};
things.forEach(callback);
Marking a function with async
ensures the function will return always return a promise, regardless its completion, if the function reaches its execution without a explicit return value, the promise will be resolved with undefined
, if it returns some value, the promise will resolve to it, and finally if something within the function throws, the promise will be rejected.
As you can see the problem is that the promises are just not being awaited by anything, and also they are not resolving to any value. The await placed in the callback does actually nothing with the value, just as in the above I'm doing .then
and doing nothing with the value.
Attempt 2: Implementing a simple forEach
function:
function forEach(array, callback) {
for(const value of array) {
callback(value);
}
}
const values = [ 'a', 'b', 'c' ];
forEach(values, (item) => {
console.log(item)
});
The above forEach is an oversimplification of the Array.prototype.forEach
method, just to show its structure, in reality the callback function is called passing the array as the this
value, and passing three arguments, the current element, the current index, and again the array instance, but we get the idea.
If we would like to implement an async
forEach
function, we would have to await the callback call:
const sleep = (time, value) => new Promise(resolve => setTimeout(resolve(value), time));
const values = [ { time: 300, value: 'a'}, { time: 200, value: 'b' }, {time: 100, value: 'c' } ];
async function forEachAsync(array, callback) {
for(const value of array) {
await callback(value);
}
}
(async () => {
await forEachAsync(values, async (item) => {
console.log(await sleep(item.time, item.value))
});
console.log('done');
})()
The above forEachAsync
function will iterate and await item by item, sequentially, normally you don't want that, if the async functions are independent, they can be done in parallel, just as I suggested in first place.
const sleep = (time, value) => new Promise(resolve => setTimeout(resolve(value), time));
const values = [ { time: 300, value: 'a'}, { time: 200, value: 'b' }, {time: 100, value: 'c' } ];
(async () => {
const promises = values.map(item => sleep(item.time, item.value));
const result = await Promise.all(promises);
console.log(result);
})()
And as you can see, even if the promises are executed in parallel, we get the results in the same order as the promises where in the array.
But the difference between this example and the first one is that this one takes only 300ms (the longest promise to resolve), and the first one takes 600ms (300ms + 200ms + 100ms).
Hope it makes it clearer.