I have this simple function that I am looking to simplify further:
setAreas() {
this.areas = ipcRenderer.sendSync('request', 'areas').map(_area => {
_area.locations = _area.locations.map(locationId => this.getLocation(locationId))
return _area
})
}
Is there any way to reduce this to a one-liner by performing the map on _area.locations and returning the updated _area?
One option would be to use Object.assign, which will return the base object being assigned to:
setAreas() {
this.areas = ipcRenderer.sendSync('request', 'areas').map(_area => (
Object.assign(_area, { locations: _area.locations.map(locationId => this.getLocation(locationId)) })
));
}
But that's not so readable. I prefer your current code.
Note that .map is appropriate for when you're transfoming one array into another. Here, you're only mutating every object in an existing array; forEach is more appropriate:
setAreas() {
this.areas = ipcRenderer.sendSync('request', 'areas');
this.areas.forEach((a) => a.locations = a.locations.map(locationId => this.getLocation(locationId)))
}
If getLocation only accepts one parameter, you can golf
a.locations = a.locations.map(locationId => this.getLocation(locationId))
down to
a.locations = a.locations.map(this.getLocation.bind(this))
(you could even remove the .bind(this) if this context isn't needed)
You can use destructuring
setAreas() {
this.areas = ipcRenderer.sendSync('request', 'areas').map(_area => ({
..._area, locations: _area.location.map(locationId => this.getLocation(locationId))
})
}
Related
I have this function in JS
function getMap(objectList) {
const objectMap = new Map();
IDS.foreach(id => {
const attribute = objectList.find(object => object.getId() === id);
if (attribute) {
objectMap.set(id, attribute);
} else {
objectMap.set(id, null);
}
}
This is a nested loop because of the find inside the for loop. How could this be simplified? If the nested loop cannot be simplified, can other parts be simplified?
Assuming object IDs are unique, it looks like all you really have to do is call getId on each object beforehand. The conditional operator may be used instead of if/else if you wish.
function getMap(objectList) {
const objectsById = new Map(
objectList.map(object => [object.getId(), object])
);
const objectMap = new Map();
for (const id of IDS) {
objectMap.set(id, objectsById.get(id) || null);
}
}
You could create an array with null entries for each ID, followed by entries for which you actually have values in objectList, and pass that array to the Map constructor:
function getMap(objectList) {
return new Map([
...IDs.map(id => [id, null]),
...objectList.map(object => [object.getId(), object])
]);
}
Using native code with a simple callback
const result = (IDS || []).map(function(id, idx, arr) {
const pos = (objectList || []).findIndex(object => object.getId() === id);
const output = [];
output[id] = (pos >= 0 ? objectList[pos] : null);
return output;
});
Hope this helps... ;D
I am trying to use the built in map() function on an Array.from () that returns some elements using Puppeteer.
The below is the code:
let res = await page.evaluate(elementPath => {
return Array.from(document.querySelectorAll(elementPath), (cin, index) => {
return {
cs: `state is ${this.s}`, // returns state is undefined
cinemaIndex: index,
cinemaId: cin.getAttribute('data-id'),
cinemaName: cin.getAttribute('data-name'),
cinemaURL: cin.getAttribute('data-url'),
};
}, {
s: 'NSW'
});
}, `div[data-state=${cinemaState}] div.top-select-option a.eccheckbox`, cinemaState);
I am not able to assign cs with variable s or cinemaState.
Wondering if you have solution
[1,2,3,4].map(function(num, index,wholeArray){
console.log(num,index,wholeArray,this.s);
},{s:"nsw"})
maps takes two argument callback and thisArg whatever u will pass at the second arg will be accessible bi this
You can assign s to the cinemaState property in your return statement using the following method:
cinemaState: this.s,
Additionally, Array.from() has a built-in map function, so you should call the map function from within Array.from() to avoid an intermediate array:
Array.from(arrayLike, mapFn); // good
Array.from(arrayLike).map(mapFn); // bad
Lastly, you may want to use quotes around cinemaState in your attribute selector within your template literal selector string:
[data-state="${cinemaState}"] // good
[data-state=${cinemaState}] // bad
Your final code should look something like this:
let res = await page.evaluate(elementPath => {
return Array.from(document.querySelectorAll(elementPath), (cin, index) => {
return {
cinemaState: this.s,
cinemaIndex: index,
cinemaId: cin.getAttribute('data-id'),
cinemaName: cin.getAttribute('data-name'),
cinemaURL: cin.getAttribute('data-url'),
};
}, {
s: 'NSW'
});
}, `div[data-state=${cinemaState}] div.top-select-option a.eccheckbox`, cinemaState);
I am able to explain this. this is what has worked for me. I had to replace the arrow function to a traditional function
let res = await page.evaluate(elementPath => {
return Array.from(document.querySelectorAll(elementPath), function (cin, index) // changed from (cin, index) =>
{
return {
cs: `state is ${this.s}`, // returns state is undefined
cinemaIndex: index,
cinemaId: cin.getAttribute('data-id'),
cinemaName: cin.getAttribute('data-name'),
cinemaURL: cin.getAttribute('data-url'),
};
}, {
s: 'NSW'
});
}, `div[data-state=${cinemaState}] div.top-select-option a.eccheckbox`, cinemaState);
const addressZip = person.get('addresses').filter((address) => address.get('legacy_id')).filter((newAddress) => (
getZIPErrors(newAddress.get('zip'))
))
when this function is executed it returns me as an
[array(0)] if it has no error
when it has an error it returns me as an [array(1)].
Instead of returning an array inside an array I just want to return a single array in this way if it has no error [] and if it has an error it should be like ['invalid']
You can implement a concatAll method within Array constructor, then use it to flatten your result:
Array.prototype.concatAll = function() {
return this.reduce((acc, curr) => acc = acc.concat(curr))
}
const addressZip = person
.get('addresses')
.filter(address => address.get('legacy_id'))
.filter(newAddress => getZIPErrors(newAddress.get('zip')))
.concatAll()
In the below, I have a function that should be filtering accountView, but for some reason it's also filtering accountCompare. Not sure why this is happening. I thought I had assigned the two seperately so that accountCompare is always a constant.
getAccount() {
this.accounts.getAccount(this.accountId).subscribe(
response => {
this.accountView = this.apiHandler.responseHandler(response);
this.accountCompare = this.apiHandler.responseHandler(response);
console.log(this.accountCompare);
},
(err) => {
this.apiHandler.errorHandler(err);
}
);
}
//then in this function, I filter accountView, however it appears to also be affecting accountCompare as well.
userDelete(id) {
if (this.accountCompare.users.some(item => item.id === id)) {
this.accountForm.value.usersToDelete.push(id);
}
this.accountView.users = this.accountView.users.filter(user => user.id !== id);
/* this.accountForm.value.usersToAdd = this.accountForm.value.usersToAdd.filter(user => id !== id); */
console.log(this.accountCompare);
}
Non-primitive values are passed by reference. This means you are actually updating a reference, not a value.
A quick hack for you :
this.accountView = JSON.parse(JSON.stringify(this.apiHandler.responseHandler(response)));
this.accountCompare = JSON.parse(JSON.stringify(this.apiHandler.responseHandler(response)));
I'm wondering if there's a concise or specific way to access values in the middle of an FP chain in JavaScript. Example:
const somestuff = [true, true, false];
let filteredCount = 0;
somestuff.filter((val) => val)
.forEach((val) => console.log(val));
Above, I'd like to set filteredCount to the length of the array returned by the filter function. The most straight-forward way is:
const somestuff = [true, true, false];
const filteredStuff = somestuff.filter((val) => val);
let filteredCount = filteredStuff.length;
filteredStuff.forEach((val) => console.log(val));
This is certainly valid but it breaks our FP chain and introduces an additional holding variable. I'm wondering if there's a convention for accessing values in the middle of the chain. Something like .once() that runs once and implicitly returns the value passed in, but nothing like that exists.
For debugging, I often use a function called tap to temporarily add a side-effect (like your console.log) to a function:
const tap = f => x => (f(x), x);
This function returns whatever it is passed, but not before calling another function with the value. For example:
const tap = f => x => (f(x), x);
const tapLog = tap(console.log);
const x = tapLog(10);
console.log("x is", x);
Your snippet basically does this:
Filter a list
(log the list)
Retrieve a length property from an array
If you construct this function using pipe or compose, you can "inject" the console.log in between without interrupting the data flow:
const countTrues = pipe(
filter(isTrue),
prop("length")
);
const countTruesWithLog = pipe(
filter(isTrue),
tap(console.log),
prop("length")
);
In a snippet:
// Utils
const isTrue = x => x === true;
const prop = k => obj => obj[k];
const tap = f => x => (f(x), x);
const filter = f => xs => xs.filter(f);
const pipe = (...fns) => x => fns.reduce((res, f) => f(res), x);
// Logic:
// Filter an array using the isTrue function
// and return the length of the result
const countTrues = pipe(
filter(isTrue),
prop("length")
);
// Create a filter with a console.log side-effect
// and return the length of the result
const countTruesWithLog = pipe(
filter(isTrue),
tap(console.log),
prop("length")
);
// App:
const somestuff = [true, true, false];
console.log("pure:");
const countA = countTrues(somestuff)
console.log(countA);
console.log("with log:")
const countB = countTruesWithLog(somestuff);
console.log(countB);
The reason there's no Array.prototype method like that, is that it has a side effect. This is something that is specifically avoided in functional programming.
However if you don't care about writing 'Pure Functions', or even the functional paradigm, you could put the side effect in your callbacks, or write a function in the Array prototype.
ie.
Array.prototype.once = function(callback) {
callback(this)
return this
}
You also have other hacky options like in the other answer
I don't think there's something like that by default. What you can do is extend Array, but I'm not really fond of extending framework classes (clashes with other once implementations for example). In this case you'd end up with:
Array.prototype.once = function once(func) {
func(this);
return this;
}
which is called like:
var filteredStuff = somestuff
.filter((val) => val)
.once(function(array) {
console.log(array.length);
})
.forEach((val) => console.log(val));
On the other hand, you can try to use default functions. One of these function that can access all items at once is reduce. Define a function once, that will call its first parameter once (:)) and you'd end up with something like:
function once(func) {
return function(accumulator, currentValue, currentIndex, array) {
if(currentIndex === 1) {
func(array);
}
return array;
}
}
which you'd be able to call like this:
var filteredStuff = somestuff
.filter((val) => val)
.reduce(once(function(array) {
console.log(array.length);
}), [0])
.forEach((val) => console.log(val));
Notice the ugly [0] to ensure once calls the passed function at least once (empty array included).
Both solutions aren't too neat, but it's the best I can come up with given the criteria.