Home > database >  Recursive async function returning undefined
Recursive async function returning undefined

Time:03-27

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?

CodePudding user response:

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));
})();

CodePudding user response:

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.

  • Related