I've got a recursive function that loops over an object. It looks for keys with the name subcomponent
(which is always an array) and performs an asynchronous function on each of subcomponent
's children, the output of which is used to replace the child's data.
In the example below populateSubcomponent()
is the async function.
Code:
async function doPopulate(data) {
Object.keys(data).map((key) => {
if (typeof data[key] === 'object') { // We want to do a recursive check on the data so are interested in any objects (arrays) at this point
if (key === 'subcomponent') { // If we have an object (array) with key `subcomponent` we want to populate each of its children
const promises = data[key].map(subcomponent => populateSubcomponent(subcomponent));
return Promise.all(promises).then((output) => {
data[key] = output;
console.log('1');
});
}
doPopulate(data[key]); // Check recursively
console.log('2');
}
console.log('3');
return data;
});
}
doPopulate(data);
My expectations are that each of the console.log
numbers should fire sequentially, but instead I get 2
, 3
, then 1
. As a result, the recursive functions runs before the async function has completed, therefore never replacing the child as intended; I get the correct result at 1
but it's not passed to 2
or 3
.
How do I best incorporate the recursive doPopulate()
call with the if
statement?
I've looked at the following SO posts:
- Using async/await inside for loop
- Best way to call an async function within map?
- Async/Await inside Array#map()
- Use async await with Array.map
but I can't relate any of the answers to my own problem, mostly due to the fact that I've got an if
statement within my recursive function and I'm not sure how to deal with that in context of the async stuff.
Edit
Thanks to everyone's comments I came up with the following:
async function doPopulate(data) {
const promisesOuter = Object.keys(data).map(async (key) => {
if (typeof data[key] === 'object') { // We want to do a recursive check on the data so are interested in any objects (arrays) at this point
if (key === 'subcomponent') { // If we have an object (array) with key `subcomponent` we want to populate each of its children
const promisesInner = data[key].map(subcomponent => populateSubcomponent(subcomponent));
data[key] = await Promise.all(promisesInner);
}
await doPopulate(data[key]); // Check recursively
}
return data;
});
return Promise.all(promisesOuter);
}
return doPopulate(data);
As this all happens within a NodeJS stream (using through2) I also needed to make the stream function async too:
const through = require('through2');
return through.obj(async (file, enc, done) => {
const data = JSON.parse(file.contents.toString());
await doPopulate(data);
file.contents = Buffer.from(JSON.stringify(data));
return done(null, file);
});