2

I'm making a recursive async function that's running a mysql query. This is the db I'm working with:

+----+-------------------+-----------+----------+-----------+---------------------+
| id | task              | completed | parentid | createdby | createdat           |
+----+-------------------+-----------+----------+-----------+---------------------+
|  1 | Clean apartment   |         0 |     NULL |         1 | 2022-03-24 00:47:33 |
|  2 | Clean bathroom    |         0 |        1 |         1 | 2022-03-24 00:47:33 |
|  3 | Clean kitchen     |         0 |        1 |         1 | 2022-03-24 00:47:33 |
|  4 | Wash shower       |         0 |        2 |         1 | 2022-03-24 00:47:33 |
|  5 | Wash toilet       |         0 |        2 |         1 | 2022-03-24 00:47:33 |
|  6 | Clean glass panes |         1 |        4 |         1 | 2022-03-24 00:47:33 |
|  7 | Clean faucet      |         0 |        4 |         1 | 2022-03-24 00:47:33 |
|  8 | Clean sink        |         0 |        3 |         1 | 2022-03-24 00:47:33 |
|  9 | Take out trash    |         1 |        3 |         1 | 2022-03-24 00:47:33 |
+----+-------------------+-----------+----------+-----------+---------------------+

If I had this db stored in an array I could run this function:

function comp(tasks, taskId) {
    var task = tasks.find(task => task.id === taskId)
    var children = tasks.filter(t => t.parentId === taskId)

    task.children = children.map(child => comp(tasks, child.id));

    return task
}

to recursively nest the child task into the main task.

The problem now is that I don't have a good understanding of async functions.

This is how far I've gotten now:

async function comp(taskId) {
    // SELECT * FROM tasks WHERE id = taskId
    var task = await con.promise().query('select * from tasks where id = ' + taskId)

    
    // SELECT * FROM tasks WHERE parentId = taskId
    var children = await con.promise().query('select * from tasks where parentid = ' + taskId)

    
    task[0][0].children = children[0].map(child => {
        comp(child.id)
    })
    console.log(task[0])
    
}

But this returns the task with undefined children:

[
  {
    id: 1,
    task: 'Clean apartment',
    completed: 0,
    parentid: null,
    createdby: 1,
    createdat: 2022-03-23T23:47:33.000Z,
    children: [ undefined, undefined ]
  }
]

In short, the result I'm looking for would look like this:

{
    id: 1,
    task: 'Clean apartment',
    completed: 0,
    parentid: null,
    createdby: 1,
    createdat: 2022-03-23T23:47:33.000Z,
    children: [ 
        {
            id: 2,
            task: 'Clean bathroom',
            completed: 0,
            parentid: 1,
            createdby: 1,
            createdat: 2022-03-23T23:47:33.000Z,
            children: [ 
                {
                    id: 4,
                    task: 'Wash shower',
                    completed: 0,
                    parentid: 2,
                    createdby: 1,
                    createdat: 2022-03-23T23:47:33.000Z,
                    children: [ ... ]
                  },
                  {
                    id: 5,
                    task: 'Wash toilet',
                    completed: 0,
                    parentid: 2,
                    createdby: 1,
                    createdat: 2022-03-23T23:47:33.000Z,
                    children: [ ... ]
                  },
            ]
          },
          {
            id: 3,
            task: 'Clean kitchen',
            completed: 0,
            parentid: 1,
            createdby: 1,
            createdat: 2022-03-23T23:47:33.000Z,
            children: [ ... ]
          },
        
        
  }

Any tips?

2
  • The callback in .map doesn't contain a return statement. There is more to do, but that's the first step. Then you have to await comp(child.id), but it won't work inside the callback. Commented Mar 27, 2022 at 1:16
  • Would it be easier for you to read in your data first from your db into an array, and then run your first comp function that knows how to work with that array? Commented Mar 27, 2022 at 1:28

2 Answers 2

3

Essentially the only problem with your code is that you don't await the results from you async function comp(). map() will return an array of Promises and you will need to await all those promises which you can do by using Promise.all(). Promise.all() returns a Promise which will resolve when all Promises in the array passed to Promise.all() are settled. If you await that your children array will be propagated as you expect it to.

Here is your code with using Promise.all(). Because I don't have a suitable database ready at the moment I replaced all your asynchronous calls to the database with an asynchronous call to a function which has an artificial delay built-in so you can see how to actually await those and that the result is actually awaited.

const data = [
  {
    id: 1,
    task: "Clean apartment",
    completed: 0,
    parentid: null,
    createdby: 1,
    createdat: "2022-03-24 00:47:33",
  },
  {
    id: 2,
    task: "Clean bathroom",
    completed: 0,
    parentid: 1,
    createdby: 1,
    createdat: "2022-03-24 00:47:33",
  },
  {
    id: 3,
    task: "Clean kitchen",
    completed: 0,
    parentid: 1,
    createdby: 1,
    createdat: "2022-03-24 00:47:33",
  },
  {
    id: 4,
    task: "Wash shower",
    completed: 0,
    parentid: 2,
    createdby: 1,
    createdat: "2022-03-24 00:47:33",
  },
  {
    id: 5,
    task: "Wash toilet",
    completed: 0,
    parentid: 2,
    createdby: 1,
    createDate: "2022-03-24 00:47:33",
  },
  {
    id: 6,
    task: "Clean glass panes",
    completed: 1,
    parentid: 4,
    createdby: 1,
    createdat: "2022-03-24 00:47:33",
  },
  {
    id: 7,
    task: "Clean faucet",
    completed: 0,
    parentid: 4,
    createdby: 1,
    createdat: "2022-03-24 00:47:33",
  },
  {
    id: 8,
    task: "Clean sink",
    completed: 0,
    parentid: 3,
    createdby: 1,
    createdat: "2022-03-24 00:47:33",
  },
  {
    id: 9,
    task: "Take out trash",
    completed: 1,
    parentid: 3,
    createdby: 1,
    createdat: "2022-03-24 00:47:33",
  },
];

async function comp(tasks, taskId) {
  // do your DB calls here (here just imitating the delay but the function are async to the result will remain the same)
  var task = await queryFind(tasks, taskId);
  var children = await queryFilter(tasks, taskId);

  // map() returns an array of promises as comp() returns a promise
  // Promise.all() returns a Promise that returns when all promises within the array are settled
  task.children = await Promise.all(
    children.map((child) => comp(tasks, child.id))
  );

  return task;
}

// this function immitates an async DB access.
async function queryFind(tasks, taskId) {
  // wait for 100 milliseconds (imitate delay)
  await sleep(100);
  return tasks.find((task) => task.id === taskId);
}

// this function immitates an async DB access.
async function queryFilter(tasks, taskId) {
  // wait for 100 milliseconds (imitate delay)
  await sleep(100);
  return tasks.filter((t) => t.parentid === taskId);
}

// delay execution. should imitage network delay here
async function sleep(ms) {
  return new Promise((resolve) => setTimeout(() => resolve(), ms));
}

// Start at task with ID 1; need to wrap the function call in an async method to be able to use await
(async () => {
  const test = await comp(data, 1);
  console.log(JSON.stringify(test, null, 4));
})();

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

2 Comments

Hi! For some reason I can't edit my post. Anyway, I added the await promise.all to the map function. Now when I run const task = comp(1) and try to console.log it, it returns Promise { <pending> }. Do you know why?
You need to await this call too as comp() is async and returns a promise. So you need to change it to const task = await comp(1) as I've done it in my code snippet in the 3rd to last line :)
0

You're waiting for the original two awaited items are done running, then you begin the next recursive call, then print without waiting for the recursive call itself.

First off, you're going to want to

await comp(child.id);

but you're also going to want to wait for every child to finish running as well.

Promise.all(array)

will wait for every promise in the array you pass into to finish and it just so happens that children[0].map(async () => {}) will return an array of promises. Go ahed and (a)wait for that as well and you should be set.

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.