Javascript : skip calling the chaining methods based on a condtion dynamically - javascript

I have an array of objects, I call a series of methods on the array. I wanted to skip in between methods based on a condition. Is there a generic way to handle it instead of having an if/else block?
When a filter is applied i wanted to call filter method in this array, when filter is not applied i want skip that.
// when filter is applied
salesActivityTypes
.filter(s=>s.selected)
.map(key => key.name);
//when filter not applied
salesActivityTypes
.map(key => key.name);
I don't want to use something like if/else block and copy-pasting the same code twice. Is there any better way to handle this?
The order of methods call matters and should'nt change

You don't need an if/else block, and if/else expression is enough and can be part of your chain:
(filteringActive
? salesActivityTypes.filter(s => s.selected)
: salesActivityTypes
).map(key => key.name);
Alternatively, in this particular case you can also pass a no-op callback to filter:
salesActivityTypes
.filter(filteringActive ? (s => s.selected) : (_ => true))
.map(key => key.name);

Just add another check inside the .filter callback for whether filtering is active.
salesActivityTypes
.filter(s => filteringActive ? s.selected : true)
.map(key => key.name);

Related

recursive rxjs switchmap ish

I want a variable states$: an observable stream of objects, with each object containing a member nextState$ of type observable. This property nextState$ sends a unique item corresponding to the next state, and so on...
example:
const states$ = of({ nextState$: createObservableWithNextState$() }).pipe(
switchMap(state => state.nextState$),
switchMap(state => state.nextState$),
switchMap(state => state.nextState$),
switchMap(state => state.nextState$),
...
)
of course it doesn't work, for two reasons at least:
I don't want the ... infinite repetition of switchMap in my code
I want to subscribe to state$ and receive each object (including the initial one in the of())
Of course I could create my own observable from scractch but before I would like to known if it would be possible with existing rxjsoperators. Any idea ?...
RxJS#expand
Expand should do what you're after pretty simply.
I assume at some point you'll reach a state without a nextState$, but you can change that condition easily.
const states$ = of({
nextState$: createObservableWithNextState$()
}).pipe(
expand(state => state.nextState$ != null? state.nextState$ : EMPTY)
);
Expand is closer to mergeMap than switchMap. You can set concurrent to 1 to make it work like concatMap. If you're really after a switchMap-like behaviour, this gets a bit more complicated.

DebounceTime Rxjs when same parameters being passed in

I have multiple components using this service and I want to debounceTime when I'm piping in the same value but if it's a different value, let it through. In this code, I want to somehow check if a types been used before
from(types).pipe(
debounceTime(1000),
map(([data, type]) => {
// mapping logic
}),
)
It very similar to this issue https://github.com/lodash/lodash/issues/2403 and I'm trying to understand it but I'm hoping to used RXJS code still
Thanks
One possible solution could be to use the pairwise operator to emit both the previous and current values, then use the debounce operator to compare the values and debounce accordingly:
from(types).pipe(
pairwise(),
debounce(([prev, cur]) => (prev === cur)
? timer(1000)
: EMPTY
),
map(([, cur]) => cur), // No further need for prev value
map(([data, type]) => {
// mapping logic
}),
)

Chaining on an Array.find() in JavaScript

I am getting into the habit of, depending on the context, converting some of my for loops to use array.find(). In so doing, I'm wondering if there's a way I can chain another operator on after the .find() in order to limit how much I grab from the object.
For instance, see the following:
currentStage = customerDoc.history.find(h => h.completed === false);
currentStageName = currentStage.name;
Since all I really want from here is the value for "currentStage.name", is there a way I can get this by chaining on after my find(), to specify I just want this property? If not, is there another way to do this in one line?
Yes you can like this, notice the use of || {} to avoid exception in case the find returns undefined
currentStage = (customerDoc.history.find(h => h.completed === false) || {}).name
But IMO you should keep it like you have right now, it's readable and easy to maintain
currentStage = customerDoc.history.find(h => h.completed === false);
currentStageName = currentStage && currentStage.name;
You could use optional chaining (which is currently a stage 3 TC39 proposal and not yet implemented in browsers) but can be used right now using babel's plugin and use it as such :
const currentStageName = customerDoc.history.find(h => !h.completed)?.name;
Use short-circuit evaluation to have a default object in case nothing if found, and destructuring to get the property you want. If nothing is found, the result would be undefined:
const { name: currentStageName } = customerDoc.history.find(h => h.completed === false) || {};
you could also chain a .map onto the find results in order to limit and/or reshape what gets returned by wrapping it in an array (and using filter if no results are found), e.g.
currentStage = [customerDoc.history.find(h => h.completed === false)].filter(h => h != undefined).map(h => ({'name':h.name}))[0]

Processing data that could be an array or a single value, without conditional

I have a variable data that could be either a single value (let's say it could be object, string, or number) or an array. I want to execute a function on every element of the array, or on the single value. Currently, I have:
let result;
if (Array.isArray(data)) {
result = data.map(d => f(d))
} else {
result = f(data);
}
I could use a ternary to the same effect, but I'd like to remove the conditional. Is there an idiomatic way of doing this?
You can add additional dimension and use .flat() to get rid of it
[5].flat() // [5]
[[5]].flat() // [5]
this way you can write:
[data].flat().map(f);
but it doesn't make sense and just wastes computional time; conditional should be faster here.
Try to just make your input consistent and always pass an array to this place (even with single element).
You can use an "esoteric looking" function I call fmap in this example (although the name isn't really correct):
function fmap (f, x) {
return (function (g) {
return g(f);
})((x && x.map && x.map.bind(x)) || function (h) { return h(x); });
}
const upper = x => x.toUpperCase();
console.log('fmap over array:', fmap(upper, ['a', 'b', 'c']));
console.log('fmap over string:', fmap(upper, 'a'));
WTF??
fmap takes a function and a value (f and x). It invokes an IIFE to determine if the value x already implements a map method. If it does, it binds that map method to the value. If the value doesn't implement a map method, it creates an anonymous function which takes in another function and calls that other function with the value x.
Finally, it returns the result of the computation.
Further thoughts
To be honest, my personal opinion is to use a conditional! It is much easier to understand and to reason about. Anyway, it is doable without a conditional statement or the ternary operator.

Current chain in lodash chained method

If I have a simple lodash chain that maps then filters an array:
lodash.chain(myarray)
.map(item=>{
if (item === 'some-condition') return [item];
})
.filter(item=>!!item)
.value();
Obviously, this is a made-up example but it relates to something simple I do all the time. Basically, a array map where some maps are not possible so 'undefined' is returned. I then filter-out all the undefined values.
Since, it is used quite lot, it makes sense to mixin it into my lodash.
So:
const lodash = _.runInContext();
function mapFilter(ary, iterator) {
return lodash.chain(ary)
.map(iterator)
.filter(item=>!!item)
.value()
}
lodash.mixin(lodash, mapFilter, {chain:true});
Obviously, we could just do the whole thing without lodash but normally, it might be part of a bigger chain. In theory, the lazy-evaluation makes it quicker.
What I really want is to tap into the current chain (if there is one) in my mixed-in method. Otherwise, I am losing the lazy-evaluation by calling value() twice.
So, if I had a longer chain:
lodash.chain(myarray)
.mapFilter( // do something) // my bespoke chainable method
.map( // do something else )
.sort()
.value();
I'd like to use the current chain (when there is one) in my bespoke method. Something like this:
// This is made-up and does not work!
const lodash = _.runInContext();
function mapFilter(ary, iterator) {
if (!!this.__currentChain) {
return this.__currentChain.map(iterator).filter(item=>!!item);
}
return lodash.chain(ary)
.map(iterator)
.filter(item=>!!item)
.value()
}
lodash.mixin(lodash, mapFilter, {chain:true});
Obviously, the above is made-up, but hopefully, it makes it clear what I am trying to achieve. I could of course, just not have my function and do a map() then a filter() but since I am doing it a lot, I'd like to have less typing. Also, the example could be longer, doing much more but still wanting to tap into the current chain.
Is this possible? That is my question. Obviously, I can think of a million and one alternative solutions but I am fine with those. Just looking for a lodash expert to say, "no not possible",or "yes, you do this".
I posted this as a comment but I feel that is what you would want either as a drop in or as something you need to check the source of how it is done and then code your own method or take pieces from it as part of your mixin etc.
The lodash _.tap method is there with the purpose of tap into" a method chain sequence in order to modify intermediate results so that you do not have to call value etc. You can use this as a starting point.
Hope this helps.
One of the ways to check if a function is called in a chain is to check whether this is LodashWrapper object or not. Then, use the first argument as an iterator when it's in a chain.
const _ = require('lodash');
const lodash = _.runInContext();
function mapFilter(array, iterator) {
if (this.constructor.name === 'LodashWrapper') {
return this.map(array).filter(item => !!item);
}
else {
return lodash.chain(array).map(iterator).filter(item => !!item).value();
}
}
lodash.mixin({ mapFilter }, { chain: true });
const filter = x => x == 2 ? [x] : null;
console.log(lodash.mapFilter([1, 2, 3], filter));
console.log(lodash.chain([1, 2, 3]).mapFilter(filter).head().value());
console.log(lodash([1, 2, 3]).mapFilter(filter).head());
By the way, when you use explicit _.chain, lodash doesn't apply shortcut fusion as you might expect. So you may want to use an implicit chaining. See Explicit chaining with lodash doesn't apply shortcut fusion for details.

Categories