I am trying to write a recursive fetch function. I am calling an endpoint that accepts a name param and returns their role and an array of direct-subordinates like so:
{
role: "CEO",
direct-subordinates: [ "john smith", "bob jones" ]
}
I then want to call the function again to request the same data for each subordinate.
Here is the code I have:
export const fetchEmployee = async (name) => {
let url = `https://url.com/to/employees/endpoint/${name}`
let req = await fetch(url)
let json = await req.json()
return json
}
export const recursiveFetchEmployees = async (initialName) => {
let json = await fetchEmployee(initialName)
const role = json[0]
const subordinates = json[1]
if (subordinates) {
return {
name: initialName,
role: role,
subordinates: subordinates['direct-subordinates'].map(async (subordinate) => {
let result = await recursiveFetchEmployees(subordinate)
return result
}),
}
} else {
return {
name: initialName,
role: role,
}
}
}
This almost works when called with recursiveFetchEmployees(employeeName).then((resp) => console.log(resp)) but the result is:
name: "robert robertson",
role: "CEO",
subordinates: (2) [Promise, Promise],
How do I change this so the function works its way down the employee hierarchy recursively producing a result like this:
{
name: "robert robertson",
role: "CEO",
subordinates: [
{
name: "john smith",
role: "Marketing Manager",
subordinates: [{
name: "mary doyle",
role: "employee",
}]
},
{
name: "bob jones",
role: "Development Manager",
subordinates: [{
name: "barry chuckle",
role: "Development Lead",
subordinates: [{
name: "billy bob",
role: "Developer",
}]
}]
},
],
}
Thanks in advance for any help or advice.
EDIT / UPDATE
Thanks to the fine answer given by #trincot the problem I had was resolved but it introduced another problem. I need to check for and filter out duplicates in the returned results. I introduced a uniqueNameArray that gets initialised with an empty array and on every call, it adds the name of the current initialName param if it does not already exist in the array. Here is my code:
export const recursiveFetchEmployees = async (initialName, uniqueNameArray = []) => {
if (!uniqueNameArray.includes(initialName)) {
uniqueNameArray.push(initialName)
let json = await fetchEmployee(initialName)
const role = json[0]
const subordinates = json[1]
if (subordinates) {
return {
name: initialName,
role: role,
subordinates: await Promise.all(
subordinates['direct-subordinates'].map(
(subordinate) => subordinate && recursiveFetchEmployees(subordinate, uniqueNameArray)
)
),
}
} else {
return {
name: initialName,
role: role,
}
}
}
}
Unfortunately when there is a duplicate it still gets called in the map function resulting in a subordinates array that looks like this:
{
name: "bob jones",
role: "Development Manager",
subordinates: [
{
name: "barry chuckle",
role: "Development Lead",
subordinates: [{
name: "billy bob",
role: "Developer",
}]
},
{
name: "james jameson",
role: "Development Lead",
subordinates: [{
name: "joey joe joe junior",
role: "Developer",
}]
},
undefined, // <-- This is where there was a duplicate
]
},
Is there a way to omit it from the promise list? What ive done above should do that as far as I can tell so Im not sure why it still returns an undefined response.
As always, any help is appreciated, thanks!
It is no surprise that .map(async ... returns an array of promise objects, as an async function always returns a promise.
You could use Promise.all here:
subordinates: await Promise.all(
subordinates['direct-subordinates'].map(recursiveFetchEmployees)
),
Note also that you can just pass recursiveFetchEmployees as callback argument to .map. There is no need to create that wrapper function.
Related
Just a bit confused as to why this magic method is returning null. It's probably very simple, but I'm using methods I wouldn't normally (bulkingCreating) and can't currently see it.
Association: Country.hasOne(Capital, { foreignKey: 'countryId' });
Populating dummy data:
const countries = await Country.bulkCreate([
{ name: 'England' },
{ name: 'Spain' },
{ name: 'France' },
{ name: 'Canada' }
]);
const capitals = await Capital.bulkCreate([
{ name: 'London' },
{ name: 'Madrid'},
{ name: 'Paris' },
{ name: 'Ottawa' }
]);
countries.forEach((country, index) => {
country.setCapital(capitals[index]);
});
const country = await Country.findOne({where: {name: 'Spain'}});
console.log(country.name, Object.keys(country.__proto__)); // Spain / magic methods
const capital = await country.getCapital();
console.log(capital); // null
The table:
Am I wrong in thinking country.getCapital() should return the relevant entry?
As you might guess setCapital should be an async function because it makes changes in DB so you need to use for instead of forEach method that does not support async callbacks:
let index = 0;
for (const country of countries) {
await country.setCapital(capitals[index]);
index += 1;
}
It would be better to create countries one by one and create capitals for them not relying on the same indexes of both collections (DB might return created records in a different order).
If you are using Sequelize 5.14+, you can do this in 1 bulkCreate using include option.
const countries = await Country.bulkCreate([
{
name: 'England',
Capital: { // This keyname should be matching with model name.
name: 'London'
}
},
{
name: 'Spain',
Capital: {
name: 'Madrid'
}
},
...
],
{
include: Capital,
returning: true // If Postgres, add this if you want the created object to be returned.
}
);
So I found examples on how to sort arrays by comparing the same field, but I need to sort them by comparing different fields. For example I have a lists of objects where each object has a field for their name and parent. I want to sort the list so that the people appear next to their parent. Example:
[
{
"name": "Bob",
"parent": "Linda"
},
{
"name": "Charlie",
"parent": "Gregory"
},
{
"name": "Linda",
"parent": "Stacy"
},
{
"name": "Andrew",
"parent": "Gabriel"
},
{
"name": "Gregory",
"parent": "Thomas"
}
]
After sorting I want Bob to be next to Linda and Charlie to be next to Gregory.
Hello #poppo8989: Welcome to Stack Overflow.
Suggestion: If you have control over the data presented in your question, you might consider storing it in a structure that better represents the relationships.
Otherwise, here's a take on solving your problem:
Explore code in TypeScript Playground
type Person = {
name: string;
parent: string;
};
type RelationshipData = {
child?: Person;
parent?: Person;
};
function getRelationships (people: Person[], person: Person): RelationshipData {
return {
child: people.find(p => p.parent === person.name),
parent: people.find(p => p.name === person.parent),
};
}
function getSortedPeople (people: Person[]): Person[] {
const sorted: Person[] = [];
const copy = [...people];
while (copy.length > 0) {
let person: Person | undefined = copy[0];
let done = false;
// set person to furthest ancestor
while (person && !done) {
const {parent} = getRelationships(copy, person);
if (parent) person = parent;
else done = true;
}
// remove from copy array and add to sorted array, repeatedly for each child
while (person) {
copy.splice(copy.indexOf(person), 1);
sorted.push(person);
person = getRelationships(copy, person).child;
}
}
return sorted;
}
function main () {
const people: Person[] = [
{name: 'Bob', parent: 'Linda'},
{name: 'Charlie', parent: 'Gregory'},
{name: 'Linda', parent: 'Stacy'},
{name: 'Andrew', parent: 'Gabriel'},
{name: 'Gregory', parent: 'Thomas'},
];
const sorted = getSortedPeople(people);
console.log(sorted); //=> Linda, Bob, Gregory, Charlie, Andrew
}
main();
Hello I try to get this output
{
name: "Saviole",
role: "ceo",
children: [
{
name: "Mary",
role: "supervisorA",
children: [
{name: "Anna", role: "worker"}
]
}, {
name: "Louis",
role: "supervisorB"
}]
}
These are the functions I wrote:
const users = [
{name: "Anna", role: "worker"},
{ name: "Mary", role: "supervisorA" },
{ name: "Louis", role: "supervisorB" },
{ name: "Saviole", role: "ceo" }
];
const recursiveAddToTree = (parent, child, grandChildren, users)=>{
let tree = {};
users.forEach(( user)=>{
if(user.role===parent){
tree ={...user}
} else if(user.role===child){
tree = {...tree, chidren:[...[user]]}
} else {
users.forEach(userChild=>{
if(userChild.role===child){
tree = {...tree, children:[...[{...userChild, chidren: [...[user]]}]]}
}
})
}
})
return tree;
}
const createSchema = users =>{
return recursiveAddToTree("ceo", "supervisorA","worker", users)
}
How do I solve this? I don't understand why it doesn't work as thought
You could take a sorted array where all users are orderd by their role under the direct role above and take an object to get a level of the roles. Then iterate and take an array of level for keeping track of the last user and inser the user according to the role/level.
This approach maintains the given order.
const
users = [
{ name: "Saviole", role: "ceo" },
{ name: "Mary", role: "supervisor" },
{ name: "Anna", role: "worker" },
{ name: "Louis", role: "supervisor" }
],
roles = { ceo: 0, supervisor: 1, worker: 2 },
tree = [],
levels = [tree];
users.forEach(user => {
const level = roles[user.role];
if (!levels[level]) {
const
temp = levels[level - 1],
last = temp[temp.length - 1];
levels[level] = [];
last.children = levels[level];
}
levels[level].push(user);
});
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I saw 2 issue in your code:
the foreach will process the users one by one, finishing by the last one: the ceo. But in your code if the processed user is the ceo your tree object is replaced by the user, so all your previous modifications are erased (I think it's the most important issue)
the roles of the supervisors are "supervisorA" and "supervisorB", but you're checking if the role is "supervisor", it'll never match, you should check if the role start with "supervisor"
I have an object with users:
const data = [
{
name: "John",
lastName: "Doe",
email: "stefa#gmail.com",
password: "123",
following: [{ id: "113"}, { id: "111" } }],
id: "112",
},
{
name: "Jane",
lastName: "Doe",
email: "dusica#gmail.com",
password: "123",
following: [{ id: "112" }],
id: "113",
},
{
name: "Mark",
lastName: "Twain",
email: "marko#gmail.com",
password: "123",
following: [],
id: "111",
},
];
As you can see all users have an array named "following", and that array contains id's of users which the user follows. I want to access that array "following" to find out which users are not followed. Let's say that we want to check the "following" array of the first user John Doe with id="112".
const followers = [];
let suggestions = null;
props.users.forEach((user) => {
if (user.id === '112') {
user.following.forEach((item) => {
followers.push(item);
});
}
});
followers.map((item) => {
suggestions = props.users.map((user) => {
if (user.id !== item.id && user.id !== '112) {
console.log(item.id);
return <div>something</div>;
}
});
});
I tried something like this, but the result is not what i expected to be. As i said, i want to return users that are not followed and render them because i want them to be visible so the user can follow them. I hope that i was understandable enough. Thanks.
It's a negative comparison.
So you want to filter out all users that a user IS following.
You can loop through each user and compare it against the following array. If the following array contains the user then don't show it.
const notFollowing = allUsers.filter(user =>
!currentUser.following.some(({ id }) => id === user.id)
);
What's the best solution to mapping 2 multiple arrays to build one by key?
I have 1 array with users who have their profile data like
var users = [{id:5, name:'Alex'}, {id:17, name:'Tom'}, {id:11, name:'John'}];
Also I have another one array of cars with key user_id To determine which machine belongs to which user.
var cars = [{id:333, name:'Nissan', user_id:11}, {id:444, name:'Toyota', user_id:17}, {id:555, name:'BMW', user_id:999}];
So we can see that Tom have Toyota and John have Nissan.
So result should be
a new array with mapped result
[{
"profile": {
"id": 17,
"name": "Tom"
},
"car": {
"id": 444,
"name": "Toyota",
"user_id": 17
}
}, {
"profile": {
"id": 11,
"name": "John"
},
"car": {
"id": 333,
"name": "Nissan",
"user_id": 11
}
}]
My solution is use forEach throw users and sub forEach throw cars and there compare user.id with car.user_id
https://jsfiddle.net/r7qwke1f/37/
You could use a two loop approach instead of a nested loop approach by collecting first all users in a hash table anbd then iterate all cars and if a user is available, then create a new result set.
var users = [{ id: 5, name: 'Alex' }, { id: 17, name: 'Tom' }, { id: 11, name: 'John' }],
cars = [{ id: 333, name: 'Nissan', user_id: 11 }, { id: 444, name: 'Toyota', user_id: 17 }, { id: 555, name: 'BMW', user_id: 999 }],
hash = {},
result = [];
users.forEach(function (user) {
hash[user.id] = user;
});
cars.forEach(function (car) {
if (hash[car.user_id]) {
result.push({ profile: hash[car.user_id], car: car });
}
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Another solution
const mappedUsersCars = users.map((user) => ({
profile: user,
car: cars.filter((car) => car.user_id === user.id)[0]
}))
You can use reduce() and find() methods to get desired result.
var users = [{id:5, name:'Alex'}, {id:17, name:'Tom'}, {id:11, name:'John'}];
var cars = [{id:333, name:'Nissan', user_id:11}, {id:444, name:'Toyota', user_id:17}, {id:555, name:'BMW', user_id:999}];
var r = users.reduce(function(r, e) {
var car = cars.find(a => a.user_id == e.id);
if(car) r.push({profile: e, car: car});
return r;
}, [])
console.log(r)
There are basically two methods you would want to use. You want to map the users to the cars, so you want to find a car for the user you are referring to
const result = users.map((user) => {
const car = cars.find(car => car.user_id === user.id);
return {
profile: user,
car,
}
})