Home > database >  Create array filled with matching data from another array
Create array filled with matching data from another array

Time:04-29

I have the following arrays:

const getUsers = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve([
                { id: 1, name: 'User 1', active: 'active'   },
                { id: 2, name: 'User 2', active: 'active'   },
                { id: 3, name: 'User 3', active: 'inactive' },
                { id: 4, name: 'User 4', active: 'active'   }
            ]);
        }, 1000);
    });
}

const getCompanies = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve([
                { id: 1, name: 'Company 1', employees: [1, 3], active: 'inactive' },
                { id: 2, name: 'Company 2', employees: [4], active: 'active' },
                { id: 3, name: 'Company 3', employees: [2], active: 'active' }
            ]);
        }, 3000);
    });
}

What I want to do is to create another Company array but with it's matching users array. For example:

{
    id: 1,
    name: 'Company 1'
    employes: [
        { id: 1, name: 'User 1', active: 'active'   },
        { id: 3, name: 'User 3', active: 'inactive' }
    ],
    active: 'inactive'
},
{ Company 2... },
{ Comapny 3... }

For now this is what I'm trying:

const getCompaniesWithUsers = async () => {
    let companies = await getCompanies();
    let users = await getUsers();

    let result = await users.filter(x => x.id === companies.map(a => a.id);  
}

But is not working at all and I have some questions:

  1. How should I handle the Promise and Timeout's from both get functions? Because if I don't use async/await I get empty consts.
  2. Which is the best way/function to solve my problem? I think that I need a combination of filter, map and find functions but I'm still learning JS so I'm reading some docs and trying to solve my issue.

CodePudding user response:

You're handling the calls to getUsers and getCompanies correctly. You need the data from both to build your list of companies with their employees.

The only modification I would recommend is using Promise.all() so you run the fetches in parallel. It won't make a difference in this snippet, but if your methods call some service, then this approach allows you to start those requests at the same time instead of waiting for the first to finish before starting the second.

You don't need to filter anything, but Array#find is handy. Use Array#map like you suggest to create a new array of employee objects out of the array of employee ids. The function passed to map will find the employee by id in the users array.

const getUsers = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve([
                { id: 1, name: 'User 1', active: 'active'   },
                { id: 2, name: 'User 2', active: 'active'   },
                { id: 3, name: 'User 3', active: 'inactive' },
                { id: 4, name: 'User 4', active: 'active'   }
            ]);
        }, 1000);
    });
}

const getCompanies = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve([
                { id: 1, name: 'Company 1', employees: [1, 3], active: 'inactive' },
                { id: 2, name: 'Company 2', employees: [4], active: 'active' },
                { id: 3, name: 'Company 3', employees: [2], active: 'active' }
            ]);
        }, 3000);
    });
}

const getCompaniesWithUsers = async () => {
  return Promise.all([getCompanies(), getUsers()]).then(([companies, users]) => {
    const filledCompanies = companies.map(c => {
      return {
        ...c,
        employees: c.employees.map(e => users.find(u => u.id === e))
      };
    });
    return filledCompanies;
  });
}

getCompaniesWithUsers().then(c => console.log(c));


If you're learning Javascript, there are some concepts in my answer that could use some explanation:

Destructuring

Instead of assigning the result of an expression to a single variable then accessing fields on the variable, you can assign the result of an expression to an object or array and access the fields directly. It's more complicated than that, but that's the gist. Here are the docs for the destructuring assignment.

The following two snippets function (roughly) identically, but the first avoids the additional allocations needed in the second. It's shorthand that may or may not be worth how confusing it looks if you don't use it often.

Promise.all([getCompanies(), getUsers()]).then(([companies, users]) => // ...
Promise.all([getCompanies(), getUsers()]).then(values => {
  const companies = values[0];
  const users = values[1];
  // ...

Spread syntax

Another shorthand trick, the spread syntax allows you to expand an iterable into an object or array.

The following two snippets function (roughly) identically, but the first again avoids the extra code needed in the second.

const a = { id: 1, name: 'Foo' };
const b = { ...a, hobby: 'Coding' };
const a = { id: 1, name: 'Foo' };
const b = { id: a.id, name: a.name, hobby: 'Coding' };

Object literals

If you have a look at this part of the code:

const filledCompanies = companies.map(c => {
  return {
    ...c,
    employees: c.employees.map(e => users.find(u => u.id === e))
  };
});

The anonymous object being returned is created using the object literal approach where a pair of curly braces surround a series of key: value pairs.

The docs say that if there are two instances of a key, the second overwrites the first. I am using that here to overwrite one of the fields on the company object while mapping.

By spreading the company object into the anonymous object being returned, the anonymous object gets the employees field from the company object. That is, it gets the array of user identifiers. I then explicitly define another employees field with the result of mapping the array of identifiers to the array of user objects. Because my definition comes after, it overwrites the array of user identifiers.

You do have to watch the environments you use this in. The docs:

In ECMAScript 5 strict mode code, duplicate property names were considered a SyntaxError. With the introduction of computed property names making duplication possible at runtime, ECMAScript 2015 has removed this restriction.

If you find you cannot use it, the following works as well:

const filledCompanies = companies.map(c => {
  const company = c;
  company.employees = c.employees.map(e => users.find(u => u.id === e));
  return company;
});

CodePudding user response:

filter() isn't async, you don't need to await it. Your earlier awaits returned arrays, you can process them normally.

You don't need filter(). You have an array of user IDs in the employees property, just map over it to get an array of the corresponding elements in users. Use find() to search the users array by ID.

const getCompaniesWithUsers = async() => {
  let companies = await getCompanies();
  let users = await getUsers();
  companies.forEach(c => c.employees = c.employees.map(eid => users.find(u => u.id == eid)));
  return companies;
}

To use this, you can use either

companies_with_users = await getCompaniesWithUsers();

if you're in an async function, or

getCompaniesWithUsers().then(companies_with_users => {
    ...
});

if not.

  • Related