4

I need to recursively call an API to walk down child entries, and return the filtered results before continuing. I was initially putting the results in an array, and then doing a .forEach, and if I found a match I needed to recurse doing so; however, that didn't work because of the problem described in the answer to this question. So, I tried to modify the answer to that question, but it's still not waiting.

const getDatabases = async (blockId) => {
  let databases = [];
  let childDatabases = [];
    
  const children = await getChildren(blockId);
  Promise.all(children.results
    .filter( (child) => {
      return (['child_database', 'database'].includes(child.type)
        || child.has_children === true);
    })
    .map( async (child) => {
      if (['child_database', 'database'].includes(child.type)) {
        return { id: child.id, title: child.child_database.title };
      } else {
        console.log(`Waiting on getDatabases for ${child.id}`); // 1
        childDatabases = await getDatabases(child.id);
        return false;
      }
    })  
  )
    .then((childDbs) => {
      console.log(`Got childDbs`); // 3, 4
      databases = 
        [...databases, ...childDatabases].filter(dbId => dbId !== false);
      return databases;
    })
    .catch((err) => console.log(err));

}

app.get('/api', async (req, res) => {
  const dashboardPage = await getDashboardPage();
  const databases = await getDatabases(dashboardPage);
  console.log('No longer awaiting getDatabases'); // 2
  ...
}

So the question is, why is 2 happening before 3 and 4, instead of after them? Shouldn't const databases = await getDatabases(dashboardPage); before 2 be waiting for all the recursive calls that pass through childDatabases = await getDatabases(child.id); after 1?

Remi Guan
  • 21,506
  • 17
  • 64
  • 87
philolegein
  • 1,099
  • 10
  • 28
  • 2
    To answer your question, have you tried `await Promise.all(code)`? Because `Promise.all()` itself a Promise, or `Promise.all` returns a Promise. – Remi Guan Jan 15 '22 at 13:01
  • 1
    Aha! Yeah, that makes sense. So the `Promise.all` is hanging out and waiting for the `async/await` inside of it, before it gets to the `.then`, but the parent function just fires off those promises and returns nothing, because no one told it to wait on them. – philolegein Jan 15 '22 at 13:10
  • 1
    Yep, exactly. Now you can self-answer your question since I don't have much time today so I won't post an answer. And writing an answer also improves your knowledge of async-await much more than reading one. I'd give you [a reference to Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all). – Remi Guan Jan 15 '22 at 13:21
  • 1
    Apart from the missing `await`, your function also is missing a `return` statement; and the usage of the variables `databases` and `childDatabases` is weird. `childDatabases` is overwritten in each `map` iteration, dropping the results from many of the recursive calls. And the `childDbs` result value from your array of promises is never used! – Bergi Jan 15 '22 at 16:39
  • @Bergi, yes — both of those were errors that I didn't catch until I fixed this one :) I don't use StackOverflow enough to be super confident of the etiquette. If it makes sense, I can go back and edit the post to not have those unrelated errors. – philolegein Jan 16 '22 at 08:23
  • Actually, one other thing. The `childDatabases` is overwritten is intentional. It's concatenated into `databases` as the recursions unwind, which is ultimately what's returned (the ultimate `return` is also missing! :) ). – philolegein Jan 16 '22 at 08:32
  • @philolegein Sure, please [edit] the question to include only those things that you actually want to ask about - as long as it doesn't invalidate any of the answers below. – Bergi Jan 16 '22 at 09:37

1 Answers1

0

The direct answer was that I needed to await the Promise.all. Otherwise, the Promise.all is hanging out and waiting for the async/await inside of it, before it gets to the .then, but the parent function just fires off those promises and returns nothing, because no one told it to wait. So, simply,

const getDatabases = async (blockId) => {
  let databases = [];
  let dbsOfChildren = [];

  const children = await getChildren(blockId);
  await Promise.all(children.results
    .filter( (child) => {

It's worth noting that for similar reasons, one can not chain more array methods after an async one. Otherwise, the next chained method will be immediately passed an array ... of unresolved promises. So, you can't, for example,

  await Promise.all(myArray
    .filter( (e) => {
      // do stuff
    })
    .map( async (e) => { 
      // do some async stuff
    })
    .filter( // filter ); // <-- this won't work
  )

Instead, you need to wait for the promises to resolve and then do your additional manipulations, so you put it in a .then:

  await Promise.all(myArray
    .filter( (e) => {
      // do stuff
    })
    .map( async (e) => { 
      // do some async stuff
    })
  )
  .then((myPromisesAreResolvedArray) => {
    return myMyPromisesAreResolvedArray.filter( // filter ); 
  })

Or, maybe better as pointed out in the comments, just store the results of the awaited promises (your modified array) in a const and go on with your code ...

  const myPromisesAreResolvedArray = await Promise.all(myArray
    .filter( (e) => {
      // do stuff
    })
    .map( async (e) => { 
      // do some async stuff
    })
  )

  return myPromisesAreResolvedArray.filter( ... );
philolegein
  • 1,099
  • 10
  • 28
  • 2
    "*so you put it in a `.then`*" - given you already use `async`/`await`, the better alternative is to use `const promiseResults = await Promise.all(myArray.filter(…).map(…)); return promiseResults.filter(…);` – Bergi Jan 15 '22 at 16:37