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:
- 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.
- 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.