0

I use recursion in the javascript async function to count the number of employees working under each manager. Try to understand what is wrong with my function and why it returns only the half amount of employees

    async function countEmployees(E, count) {
        if (E.employees === 'undefined' || E.employees == null) {
            return 0
        }
        else {
            count += E.employees.length
            E.employees.forEach(async emp => {
                console.log('id:', emp.id, 'count:', count)
                await countEmployees(emp, count)
            })
        }
    }

UPDATE: I think I solved it! Was constantly overriding the count Now the modified version works

var count 
async function countEmployees(E) {

    if (E.employees === 'undefined' || E.employees == null) {
        return 0
    }
 
    else {
        count += E.employees.length
        for (const ind in E.employees) {

            if (Object.hasOwnProperty.call(E.employees, ind)) {
                const emp = E.employees[ind];
                await countEmployees(emp)
            }
           
        }

    }
    
}

setTimeout(() => {
   console.log(count) }, 5000);
Aim
  • 1
  • 2

4 Answers4

1

I just made this approach. I'm not sure if you really need async functionality, but here you go:

// Assuming you have a data structure similar to this

let employees = {
  id: 1,
  employees: [
    {
      id: 11,
      employees: [
        {
          id: 111,
          employees: []
        },
        {
          id: 112,
          employees: []
        }
      ]
    },
    {
      id: 12,
      employees: [
        {
          id: 121,
          employees: []
        },
        {
          id: 122,
          employees: []
        }
      ]
    }
  ]
};

let count = 0;

function recurseEmployees(employee) {
  count++;
  employee.employees.forEach(emp => {
    recurseEmployees(emp);
  });
}

recurseEmployees(employees);

console.log(count);
Patrick
  • 458
  • 6
  • 12
  • [`forEach` cannot await things...](https://stackoverflow.com/q/37576685/8376184) – FZs Apr 14 '21 at 07:16
  • @FZs Thanks for pointing that out. Tested it with for ... of and then due to the async behaviour, count was logged before the last employee was counted. – Patrick Apr 14 '21 at 07:32
  • This is not the best solution, but as the question is quite unclear, you can't make it much better. `async` is unnecessary here, and makes the code very complex. – FZs Apr 14 '21 at 07:39
  • thank you all for the help and clarification! – Aim Apr 14 '21 at 07:56
  • 1
    Your function doesn't return a promise at any point to be resolved later after some asynchronous event like a request finished or a timeout, so in fact it will be run straight away and won't return until the recursion finishes, so async/await is not doing anything here. And anyway, as @FZs points out, forEach can't iterate asynchronously. – ejosafat Apr 14 '21 at 09:54
  • Yes, I know. I removed async/await, because it's not working with my solution. – Patrick Apr 14 '21 at 09:55
1

Using @Patrick's data structure, this is the async and sync version:

Note:

  1. Your code was using null or undefined, and Patrick used an empty array. You can always modify it to suit your data structure.
  2. Note that undefined is a primitive type, with the single value undefined. You don't need to double quote it as in your code
  3. To check whether something is null or undefined, we really can just use foo == null, which is the double equal. This is called nullish comparison. Likewise, we can use foo != null to check it is not null and not undefined
  4. Recursion is more elegant if you (a) solve a little bit of the problem, and then let your solution solve the "simpler version(s)" of your problem, which is shown in the code below. Note that the reduce() was simply to add up the array, and I didn't use it that way in the synchronous version. But either way, it is fine.
  5. If you use async function, note that it returns a promise, so we you get the counts, you have to use Promise.all() to say, all counts have been obtained, and then you add up the counts

let employees = {
  id: 1,
  employees: [{
      id: 11,
      employees: [{
          id: 111,
          employees: []
        },
        {
          id: 112,
          employees: []
        }
      ]
    },
    {
      id: 12,
      employees: [{
          id: 121,
          employees: []
        },
        {
          id: 122,
          employees: []
        }
      ]
    }
  ]
};

// Asynchronous

async function recurseEmployees(employee) {
  let count = 1;

  if (employee.employees && employee.employees.length !== 0) {
    const allChildrenCounts = await Promise.all(employee.employees.map(emp => recurseEmployees(emp)));
    count += allChildrenCounts.reduce((a, b) => a + b);
  }
  return count;
}

recurseEmployees(employees).then(c => console.log(c));

// Synchronous

function recurseEmployeesSync(employee) {
  let count = 1;

  if (employee.employees && employee.employees.length !== 0) {
    for (const emp of employee.employees)
      count += recurseEmployeesSync(emp);
  }
  return count;
}

console.log(recurseEmployeesSync(employees));
kla
  • 63
  • 1
  • 8
0

I see no reason for the asynchronicity unless actually fetching an employee with her direct reports is itself an asynchronous call. If that's the case, I think we can do this more simply using Promise.all, using something like this:

const countEmployees = async (id) => 
  1 + sum (await Promise.all (
    (await fetchEmployee (id)) .employees .map (countEmployees)
  ))

This depends on an obvious sum helper which totals an array of numbers. We fetch the employee by id, find its employees array property, mapping countEmployees over those results, call Promise.all on the returned array, await the result and then sum the totals returned, adding 1 for the current employee.

This is naive, and would need some work to make it production-ready. It does not handle failure cases. If fetchEmployee was rejected, this would just fail. But I leave fixing that up as an exercise for the reader.

Here's an implementation with a dummy fetchEmployee function that returns a promise for the elements of a hierarchy where 2, 3, and 4 report to 1, where 5 and 6 report to 2, ..., and where 12 reports to 9.

const sum = (ns) => ns.reduce ((a, b) => a + b, 0)

const countEmployees = async (id) => 
  1 + sum (await Promise.all (
    (await fetchEmployee (id)) .employees .map (countEmployees)
  ))

countEmployees (1) .then (console .log)
<script>/* Dummy implementation */ const fetchEmployee = ((emps) => 
  async (id, employees = emps [id]) => Promise .resolve ({id, employees})
)([[], [2, 3, 4], [5, 6], [7, 8], [9], [10, 11], [], [], [], [12], [], [], []])</script>
Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
0

Here is a one-liner, using object-scan

// const objectScan = require('object-scan');

const employees = { id: 1, employees: [{ id: 11, employees: [{ id: 111, employees: [] }, { id: 112, employees: [] }] }, { id: 12, employees: [{ id: 121, employees: [] }, { id: 122, employees: [] }] }] };

const count = objectScan(['**(^employees$).id'], { useArraySelector: false, rtn: 'count' });

console.log(count(employees));
// => 7
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan@14.3.1"></script>

Disclaimer: I'm the author of object-scan

vincent
  • 1,953
  • 3
  • 18
  • 24