Get to intermediate values in middle of a functional programming chain - javascript

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.

Related

Can method chaining be implemented the way built-in functions in Javascript are implemented?

I think there is something that i'm missing about method chaining. To me it feels incomplete.
Method chaining works by having each method return this so that another method on that object can be called. However, the fact that the return value is this and not the result of the function seems inconvenient to me.
Here is a simple example.
const Obj = {
result: 0,
addNumber: function (a, b) {
this.result = a + b;
return this;
},
multiplyNumber: function (a) {
this.result = this.result * a;
return this;
},
}
const operation = Obj.addNumber(10, 20).multiplyNumber(10).result
console.log(operation)
key points:
Every method in the chain Obj.addNumber(10, 20).multiplyNumber(10) returns this.
The last part of the chain .result is the one that returns a value other than this.
The problem with this approach is that it require you to tack on a property / method to get a value at the end other thanthis.
Compare this with built-in functions in JavaScript.
const str = " SomE RandoM StRIng "
console.log(str.toUpperCase()) // " SOME RANDOM STRING "
console.log(str.toUpperCase().trim()) // "SOME RANDOM STRING"
console.log(str.toUpperCase().trim().length) // 18
key points:
Each function in the chain returns the result of the function not this (maybe this is done under the hood)
No property / method is required at the end of the chain just to get the result.
Can we implement method chaining to behave the way built-in functions in Javascript behave?
First of all, each of your console.log doesn't return properly:
console.log(str.toUpperCase.trim) //undefined
It returns undefined because str.toUpperCase returns the function object and does not execute the function itself so it won't work
The only correct usage is
console.log(str.toUpperCase().trim()
Now about your question, it is pretty easy to do it without a result and it is much more efficient.
Everything in javascript has a method called valueOf(), here is my example of calling everything like that for numbers, though I prefer just making functions instead of Objects.
const Obj = {
addNumber: function (a = 0) {
return a + this.valueOf();
},
multiplyNumber: function (a = 1) {
return a*this.valueOf();
},
}
const nr = 2;
Object.keys(Obj).forEach(method => {
Number.prototype[method] = Obj[method];
})
console.log(Number.prototype); // will print out addNumber and multiplyNumber
// Now You can call it like this
console.log(nr.addNumber().multiplyNumber()); // Prints out 2 because it becomes (nr+0)*1
console.log(nr.addNumber(3).multiplyNumber(2)) // Prints out 10;
I think you are misunderstanding what method chaining actually is. It is simply a shorthand for invoking multiple methods without storing each intermediate result in a variable. In other words, it is a way of expressing this:
const uppercase = " bob ".toUpperCase()
const trimmed = uppercase.trim()
as this
const result = " bob ".toUpperCase().trim()
Nothing special is happening. The trim method is simply being called on the result of " bob ".toUpperCase(). Fundamentally, this boils down to operator precedence and the order of operations. The . operator is an accessor, and is evaluated from left to right. This makes the above expression equivalent to this (parens used to show order of evaluation):
const result = (" bob ".toUpperCase()).trim()
This happens regardless of what is returned by each individual method. For instance, I could do something like this:
const result = " bob ".trim().split().map((v,i) => i)
Which is equivalent to
const trimmed = " bob ".trim()
const array = trimmed.split() //Note that we now have an array
const indexes = array.map((v,i) => i) //and can call array methods
So, back to your example. You have an object. That object has encapsulated a value internally, and adds methods to the object for manipulating the results. In order for those methods to be useful, you need to keep returning an object that has those methods available. The simplest mechanism is to return this. It also may be the most appropriate way to do this, if you actually are trying to make the object mutable. However, if immutability is an option, you can instead instantiate new objects to return, each of which have the methods you want in the prototype. An example would be:
function MyType(n) {
this.number = n
}
MyType.prototype.valueOf = function() {
return this.number
}
MyType.prototype.add = function(a = 0) {
return new MyType(a + this)
}
MyType.prototype.multiply = function(a = 1) {
return new MyType(a * this)
}
const x = new MyType(1)
console.log(x.add(1)) // { number: 2 }
console.log(x.multiply(2)) // { number: 2 }
console.log(x.add(1).multiply(2)) // { number: 4 }
console.log(x.add(1).multiply(2) + 3) // 7
The key thing to note about this is that you are still using your object, but the valueOf on the prototype is what allows you to directly utilize the number as the value of the object, while still making the methods available. This is shown in the last example, where we directly add 3 to it (without accessing number). It is leveraged throughout the implementation by adding this directly to the numeric argument of the method.
Method chaining is the mechanism of calling a method on another method of the same object in order to get a cleaner and readable code.
In JavaScript method chaining most use the this keyword in the object's class in order to access its method (because the this keyword refers to the current object in which it is called)
When a certain method returns this, it simply returns an instance of the object in which it is returned, so in another words, to chain methods together, we must make sure that each method we define has a return value so that we can call another method on it.
In your code above, the function addNumber returns the current executing context back from the function call. The next function then executes on this context (referring to the same object), and invokes the other functions associated with the object. it's is a must for this chaining to work. each of the functions in the function chaining returns the current Execution Context. the functions can be chained together because the previous execution returns results that can be processed further on.
This is part of the magic and uniqueness of JavaScript, if you're coming from another language like Java or C# it may look weird for you, but the this keyword in JavaScript behaves differently.
You can avoid the necessity of this and be able to return a value implicitly, using a Proxy object with a get-trap.
Here you find a more generic factory for it.
const log = Logger();
log(`<code>myNum(42)
.add(3)
.multiply(5)
.divide(3)
.roundUp()
.multiply(7)
.divide(12)
.add(-1.75)</code> => ${
myNum(42)
.add(3)
.multiply(5)
.divide(3)
.roundUp()
.multiply(7)
.divide(12)
.add(-1.75)}`,
);
log(`\n<code>myString(\`hello world\`)
.upper()
.trim()
.insertAt(6, \`cruel coding \`)
.upper()</code> => ${
myString(`hello world`)
.upper()
.trim()
.insertAt(6, `cruel coding `)
.upper()
}`);
log(`<br><code>myString(\`border-top-left-radius\`).toUndashed()</code> => ${
myString(`border-top-left-radius`).toUndashed()}`);
// the proxy handling
function proxyHandlerFactory() {
return {
get: (target, prop) => {
if (prop && target[prop]) {
return target[prop];
}
return target.valueOf;
}
};
}
// a wrapped string with chainable methods
function myString(str = ``) {
const proxyHandler = proxyHandlerFactory();
const obj2Proxy = {
trim: () => nwProxy(str.trim()),
upper: () => nwProxy(str.toUpperCase()),
lower: () => nwProxy(str.toLowerCase()),
insertAt: (at, insertStr) =>
nwProxy(str.slice(0, at) + insertStr + str.slice(at)),
toDashed: () =>
nwProxy(str.replace(/[A-Z]/g, a => `-${a.toLowerCase()}`.toLowerCase())),
toUndashed: () => nwProxy([...str.toLowerCase()]
.reduce((acc, v) => {
const isDash = v === `-`;
acc = { ...acc,
s: acc.s.concat(isDash ? `` : acc.nextUpcase ? v.toUpperCase() : v)
};
acc.nextUpcase = isDash;
return acc;
}, {
s: '',
nextUpcase: false
}).s),
valueOf: () => str,
};
function nwProxy(nwStr) {
str = nwStr || str;
return new Proxy(obj2Proxy, proxyHandler);
}
return nwProxy();
}
// a wrapped number with chainable methods
function myNum(n = 1) {
const proxyHandler = proxyHandlerFactory();
const obj2Proxy = {
add: x => nwProxy(n + x),
divide: x => nwProxy(n / x),
multiply: x => nwProxy(n * x),
roundDown: () => nwProxy(Math.floor(n)),
roundUp: () => nwProxy(Math.ceil(n)),
valueOf: () => n,
};
function nwProxy(nwN) {
n = nwN || n;
return new Proxy(obj2Proxy, proxyHandler);
}
return nwProxy();
}
// ---- for demo ---- //
function Logger() {
const report =
document.querySelector("#report") ||
document.body.insertAdjacentElement(
"beforeend",
Object.assign(document.createElement("pre"), {
id: "report"
})
);
return (...args) => {
if (!args.length) {
return report.textContent = ``;
}
args.forEach(arg =>
report.insertAdjacentHTML(`beforeEnd`,
`<div>${arg.replace(/\n/g, `<br>`)}</div>`)
);
};
}
body {
font: 12px/15px verdana, arial;
margin: 0.6rem;
}
code {
color: green;
}

How does the reduce function work when a function is returned? Would also like to know more about compose and composition

The following code is written in a functional manner. There is a user object which is going to make a purchase. The process is as follow: an item is added to the cart, tax is added, the item is bought and pushed into the purchase history array of the user. Then finally the original cart is emptied. This is all done through the use of the reduce function that has a compose function passed into it.
What I'm struggling to understand is how the reduce function works in this case. From my understanding here the compose function is passed in as the callback function for reduce. within the compose function f represents the accumulator and g represents an item in the array.
const user = {
name: 'Kim',
active: true,
cart: [],
purchases: []
}
const compose = (f,g) => (...args) => {
console.log(1, f);
console.log(2, g);
return f(g(...args));
}
// The below code is commented out so that I can visualize it in a more simple manner
//purchaseItem (
// emptyCart,
// buyItems,
//applyTaxToItems,
// addItemToCart
//)(user, {name: 'laptop', price: 244});
function purchaseItem(...fns) {
console.log(fns);
return fns.reduce(compose);
}
// Here is each piece broken down to be passed into reduce and the arguments the compose function takes
console.log([emptyCart , buyItems, applyTaxToItems, addItemToCart].reduce(compose)(user, {name: 'laptop', price: 244}));
function addItemToCart(user, item) {
const updateCart = user.cart.concat(item);
return Object.assign({}, user, { cart: updateCart });
}
function applyTaxToItems(user, item) {
return user
}
function buyItems(user, item) {
return user
}
function emptyCart(user, item) {
return user
}
The output is as follows:
1 [Function]
2 [Function: addItemToCart]
1 [Function]
2 [Function: applyTaxToItems]
1 [Function: emptyCart]
2 [Function: buyItems]
{ name: 'Kim',
active: true,
cart: [ { name: 'laptop', price: 244 } ],
purchases: [] }
I tried to map the flow of the f and g element. I understand that f will hold whatever value the compose function returns but why is the initial value an anonymous function. Furthermore, why does the item element start with the last element in the array and work backwards? I'm also confused as to why emptyCart function becomes the f value in the last cycle of the reduce. If anyone could explain this to me I would be extremely grateful. Thank you.
Your logs aren't mapping the flow of execution correctly.
You're logging f first and g second, but for f(g(x)), g is evaluated first and f second. f(g(x) can be read "f after g of x" or "f of g of x".
In the same way, when you reduce an array of functions with a reducer like f(g(...x)), they're going to be evaluated in reverse order, since your resulting function behaves like f(g(h(x))).
See the code below for more of an explanation. compose2 is the same as your compose function, but with more heavy duty logging.
If you run the code below, you might get a better idea of what's going on. To evaluate the function created by the reduction/composition, we evaluate multiple functions of the form f(g(...args)).
Notice how the results of g(...args) all propagate up before the final result propagates back down.
const compose2 = (f, g, i) => {
const name = `${f.name} after ${g.name}`;
const h = {[name]: (...args) => {
console.log(i, 'f:', f.name);
console.log(i, 'g:', g.name);
console.log(i, 'args to g:', ...args);
console.log(i, 'g(...args):', g(...args));
console.log(' ');
const res = f(g(...args));
console.log(i, 'result:', res);
return res;
}}[name];
return h;
}
const f_xe = x => x + 'e',
f_xd = x => x + 'd',
f_xc = x => x + 'c',
f_xy = (x, y) => x + y;
console.log([f_xe, f_xd, f_xc, f_xy].reduce(compose2)('a','b'));

Memoize a curried function

const f = (arg1) => (arg2) => { /* returns something */ }
Is it possible to memoize f with regard to the 2 arguments, namely:
f(1)(2);
f(1)(3); // Cache not hit
f(4)(2); // Cache not hit
f(1)(2); // Cache hit
You could take a Map as cache and take nested maps for all following arguments.
This cache works for arbitrary count of arguments and reuses the values from the former calls.
It works by taking a curried function and an optional Map. If the map is not supplied, a new map is created which serves as base cache for all other calls of the returned closure or the final result.
The inner function takes a single argument and checks if this value is in the map.
If not, call the curried function and check the returned value
if function, create a new closure over the function and a new map,
if no function take the result,
as value for a new element of the map.
Finally return the value from the map.
const cached = (fn, map = new Map()) => arg => {
const inCache = map.has(arg);
const hint = inCache ? 'in cache' : 'not in cache';
console.log(arg, hint);
if (!inCache) {
const value = fn(arg);
const result = typeof value === 'function' ? cached(value, new Map()) : value;
map.set(arg, result);
}
return map.get(arg);
};
const f = a => b => c => a * b * c; // the original curried function
const g = cached(f); // its cached variant
console.log(g(1)(2)(5)); // not not not 10
console.log(g(1)(3)(4)); // in not not 12
console.log(g(4)(2)(3)); // not not not 24
console.log(g(1)(2)(6)); // in in not 12
console.log(g(4)(2)(3)); // in in in 24
.as-console-wrapper { max-height: 100% !important; top: 0; }
Interesting question — you could have independent caches for each function. The cache on the outside function will hold functions. Each inside function could get its own independent cache. So calling f(10)(1) followed by f(10)(2) would result in calling a cached version of the inside function. Calling f(10)(1) again would hit both caches:
function getCachedF() {
// outer cache holds functions keyed to argument
let outer_memo = {}
const f = (arg1) => {
if (!outer_memo.hasOwnProperty(arg1)) {
// Create inner function on outer cache
// each inner function needs its own cache
// because it will return different values
// given different outer function calls
let inner_memo = {}
console.log("outer cache miss")
outer_memo[arg1] = (arg2) => {
// just a normal memoized function
// cache is simple key:value pair
if (!inner_memo.hasOwnProperty(arg2)) {
console.log("inner cache miss")
inner_memo[arg2] = arg1 + arg2
}
return inner_memo[arg2]
}
}
return outer_memo[arg1]
}
return f
}
let f = getCachedF()
// both caches miss
console.log("3+5", f(3)(5))
// cached result
console.log("3+5", f(3)(5))
// only inside cache hit
console.log("3+8", f(3)(8))
// inside cache only hits if both args are the same
console.log("10+8", f(10)(8))
Another alternative would be to have single cache with keys that are a combination of both arguments, but then the inner function would always have to be called.
This probably isn't the canonical memoization function.
A function that needs to cache its result is given a cache function that is used to store and retrieve previous results:
const sum = memo(cache => a => b => cache(`${a}+${b}`, () => a + b));
// ^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^
// A B C
A — The cache function is provided by the memo function.(A memoized function can opt-out from caching some results if necessary.)
B — A unique key for the result. (e.g. cache['1+2'] = 3)
C — A thunk that returns the result.(So we can check if we have it already before computing it.)
This supports both curried and non-curried functions but also functions that return a function as a value.
The memo function can be implemented as follow:
const memo = fn => {
const ns = Symbol();
const cache = (key, thunk) => cache[ns][key] ??= thunk();
cache[ns] = {};
return fn(cache);
};
I quite like the logical nullish assignment operator for managing the cache:
a ??= answer()
The expression on the right is evaluated and assigned to a if and only if a is not already defined. Then it returns the value of a:
const answer = () => (console.log('returning the answer'), 42);
let a;
a ??= answer();
//=> LOG: returning the answer
//=> 42
a ??= answer();
//=> 42
a ??= 40;
//=> 42
I've used a symbol to hide the actual cache set on the cache function. A symbol is not returned when enumerating properties of an object:
const foo = {};
const key1 = Symbol();
const key2 = 'bar';
foo[key1] = 42;
foo[key2] = 41;
Object.keys(foo);
//=> ['bar']
Object.entries(foo);
//=> [['bar', 41]]
Demo
// curried memoized function
const sum = memo(cache => a => b =>
cache(`${a}+${b}`,
() => (console.log(`computing ${a}+${b}…`), a+b)));
console.log(sum(1)(2));
console.log(sum(1)(2));
console.log(sum(1)(2));
// non-curried memoized function
const mul = memo(cache => (a, b) =>
cache(`${a}*${b}`,
() => (console.log(`computing ${a}*${b}…`), a*b)));
console.log(mul(2, 3));
console.log(mul(2, 3));
console.log(mul(2, 3));
// function-returning function
const deferred_sum = memo(cache => a => b =>
cache(`${a}+${b}`,
() => (console.log(`defer computing ${a}+${b}…`), () => a+b)));
console.log(deferred_sum(1)(2)());
console.log(deferred_sum(1)(2)());
console.log(deferred_sum(1)(2)());
<script>
const memo = fn => {
const ns = Symbol();
const cache = (key, thunk) => cache[ns][key] ??= thunk();
cache[ns] = {};
return fn(cache);
};
</script>
You can not to pass map to every function.
You can do like the next:
const memoize = fn => {
const cache = {};
return (...args) => {
const curriedFn = fn(...args);
return (...next) => {
const key = // generate your key
if (key in cache) return cache[key];
return (cache[key] = curriedFn(...next));
}
}
}

Nested Validations With Folktale

I've been using Folktale's Validation on a new project and I've found it really useful, but I have hit a wall with the need for sequential validations. I have a config object and I need to perform the following validations:
is is an Object?
are the object's keys valid (do they appear on a whitelist)?
are the values of the keys valid?
Each validation depends on the previous validation - if the item isn't an object, validating its keys is pointless (and will error), if the object has no keys, validating their values are pointless. Effectively I want to short-circuit validation if the validation fails.
My initial thought was to use Result instead of Validatio, but mixing the two types feels confusing, and I already havevalidateIsObject` defined and used elsewhere.
My current (working but ugly) solution is here:
import { validation } from 'folktale';
import { validateIsObject } from 'folktale-validations';
import validateConfigKeys from './validateConfigKeys';
import validateConfigValues from './validateConfigValues';
const { Success, Failure } = validation;
export default config => {
const wasObject = validateIsObject(config);
let errorMessages;
if (Success.hasInstance(wasObject)) {
const hadValidKeys = validateConfigKeys(config);
if (Success.hasInstance(hadValidKeys)) {
const hasValidValues = validateConfigValues(config);
if (Success.hasInstance(hasValidValues)) {
return Success(config);
}
errorMessages = hasValidValues.value;
} else {
errorMessages = hadValidKeys.value;
}
} else {
errorMessages = wasObject.value;
}
return Failure(errorMessages);
};
I initially took the approach of using nested matchWiths, but this was even harder to read.
How can I improve on this solution?
You can write a helper that applies validation rules until a Failure is returned. A quick example:
const validateUntilFailure = (rules) => (x) => rules.reduce(
(result, rule) => Success.hasInstance(result)
? result.concat(rule(x))
: result,
Success()
);
We use concat to combine two results. We use Success.hasInstance to check whether we need to apply the next rule. Your module will now be one line long:
export default config => validateUntilFailure([
validateIsObject, validateConfigKeys, validateConfigValues
]);
Note that this implementation doesn't return early once it sees a Failure. A recursive implementation might be the more functional approach, but won't appeal to everyone:
const validateUntilFailure = ([rule, ...rules], x, result = Success()) =>
Failure.hasInstance(result) || !rule
? result
: validateUntilFailure(rules, x, result.concat(rule(x)))
Check out the example below for running code. There's a section commented out that shows how to run all rules, even if there are Failures.
const { Success, Failure } = folktale.validation;
const validateIsObject = (x) =>
x !== null && x.constructor === Object
? Success(x)
: Failure(['Input is not an object']);
const validateHasRightKeys = (x) =>
["a", "b"].every(k => k in x)
? Success(x)
: Failure(['Item does not have a & b.']);
const validateHasRightValues = (x) =>
x.a < x.b
? Success(x)
: Failure(['b is larger or equal to a']);
// This doesn't work because it calls all validations on
// every item
/*
const validateItem = (x) =>
Success().concat(validateIsObject(x))
.concat(validateHasRightKeys(x))
.concat(validateHasRightValues(x))
.map(_ => x);
*/
// General validate until failure function:
const validateUntilFailure = (rules) => (x) => rules.reduce(
(result, rule) => Success.hasInstance(result)
? result.concat(rule(x))
: result,
Success()
);
// Let's try it out!
const testCases = [
null,
{ a: 1 },
{ b: 2 },
{ a: 1, b: 2 },
{ a: 2, b: 1 }
];
const fullValidation = validateUntilFailure([
validateIsObject,
validateHasRightKeys,
validateHasRightValues
]);
console.log(
testCases
.map(x => [x, fullValidation(x)])
.map(stringifyResult)
.join("\n")
);
function stringifyResult([input, output]) {
return `input: ${JSON.stringify(input)}, ${Success.hasInstance(output) ? "success:" : "error:"} ${JSON.stringify(output.value)}`;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/folktale/2.0.1/folktale.min.js"></script>

RxJS: debounce a stream only if distinct

I want to debounce a stream - but only if the source value is the same as before. How would I do this with RxJS 5?
I do not want to emit a value if the value is the same and I emitted it previously within a specified time window. I should be able to use the value from the stream - or compare function similar to distinctUntilChanged.
It depends on what you're trying to do; I came upon this question when I was trying to do something similar, basically debouncing but with different debounces for different values of an object.
After trying the solution from jayphelps I couldn't get it to behave as I wanted. After much back and forth, turns out there is an in built easy way to do it: groupby.
const priceUpdates = [
{bid: 10, id: 25},
{bid: 20, id: 30},
{bid: 11, id: 25},
{bid: 21, id: 30},
{bid: 25, id: 30}
];//emit each person
const source = Rx.Observable.from(priceUpdates);
//group by age
const example = source
.groupBy(bid => bid.id)
.mergeMap(group$ => group$.debounceTime(500))
const subscribe = example.subscribe(val => console.log(val));
Output:
[object Object] {
bid: 11,
id: 25
}
[object Object] {
bid: 25,
id: 30
}
Jsbin: http://jsbin.com/savahivege/edit?js,console
This code will group by the bid ID and debounce on that, so therefore only send the last values for each.
I'm not aware of any way to do this with without creating your own operator because you need to maintain some sort of state (the last seen value).
One way looks something like this:
// I named this debounceDistinctUntilChanged but that might not be
// the best name. Name it whatever you think makes sense!
function debounceDistinctUntilChanged(delay) {
const source$ = this;
return new Observable(observer => {
// Using an object as the default value
// so that the first time we check it
// if its the same its guaranteed to be false
// because every object has a different identity.
// Can't use null or undefined because source may
// emit these!
let lastSeen = {};
return source$
.debounce(value => {
// If the last value has the same identity we'll
// actually debounce
if (value === lastSeen) {
return Observable.timer(delay);
} else {
lastSeen = value;
// This will complete() right away so we don't actually debounce/buffer
// it at all
return Observable.empty();
}
})
.subscribe(observer);
});
}
Now that you see an implementation you may (or may not) find it differs from your expectations. Your description actually left out certain details, like if it should only be the last value you keep during the debounce time frame or if it's a set--basically distinctUntilChanged vs. distinct. I assumed the later.
Either way hopefully this gives you a starting point and reveals how easy it is to create custom operators. The built in operators definitely do not provide solutions for everything as-is, so any sufficiently advanced app will need to make their own (or do the imperative stuff inline without abstracting it, which is fine too).
You can then use this operator by putting it on the Observable prototype:
Observable.prototype.debounceDistinctUntilChanged = debounceDistinctUntilChanged;
// later
source$
.debounceDistinctUntilChanged(400)
.subscribe(d => console.log(d));
Or by using let:
// later
source$
.let(source$ => debounceDistinctUntilChanged.call($source, 400))
.subscribe(d => console.log(d));
If you can, I recommend truly understanding what my code does, so that in the future you are able to easily make your own solutions.
Providing an answer for RxJS 6+ with the method suggested by #samberic in an ngrx effect to group actions coming from a same source id with RxJS 6.
this.actions$.pipe(
ofType(actionFoo, actionBar), // Two different ngrx action with an id property
groupBy(action => action.id), // Group by the id from the source
mergeMap(action => action.pipe(
debounceTime(5000)
))
).pipe(
// Do whatever it is that your effect is supposed to do!
)
Here is my RXJS 6+ version in typescript that works 100% as originally requested. Debounce (restart timer) on every new source value. Emit value only if the new value is different from the previous value or the debounce time has expired.
// custom rxjs operator to debounce while the source emits the same values
debounceDistinct<T>(delay: number) {
return (source: Observable<T>): Observable<T> => {
return new Observable(subscriber => {
let hasValue = false;
let lastValue: T | null = null;
let durationSub: Subscription = null;
const emit = () => {
durationSub?.unsubscribe();
durationSub = null;
if (hasValue) {
// We have a value! Free up memory first, then emit the value.
hasValue = false;
const value = lastValue!;
lastValue = null;
subscriber.next(value);
}
};
return source.subscribe(
(value: T) => {
// new value received cancel timer
durationSub?.unsubscribe();
// emit lastValue if the value has changed
if (hasValue && value !== lastValue) {
const value = lastValue!;
subscriber.next(value);
}
hasValue = true;
lastValue = value;
// restart timer
durationSub = timer(delay).subscribe(() => {
emit();
});
},
error => {
},
() => {
emit();
subscriber.complete();
lastValue = null;
});
});
}
}
Another possibility, not sure if supported with rxjs5 though:
source$.pipe(
pairwise(),
debounce(([a, b]) => {
if (a === b) {
return interval(1000)
}
return of();
}),
map(([a,b]) => b)
)
.subscribe(console.log);
https://stackblitz.com/edit/typescript-39nq7f?file=index.ts&devtoolsheight=50
update for rxjs 6 :
source$
.pipe(
// debounceTime(300), optionally un-comment this to add debounce
distinctUntilChanged(),
)
.subscribe(v => console.log(v))
This rxjs6+ operator will emit when the source 'value' has changed or when some 'delay' time has passed since last emit (even if 'value' has not changed):
export function throttleUntilChanged(delay: number) {
return (source: Observable<any>) => {
return new Observable(observer => {
let lastSeen = {};
let lastSeenTime = 0;
return source
.pipe(
flatMap((value: any) => {
const now = Date.now();
if (value === lastSeen && (now - lastSeenTime) < delay ) {
return empty();
} else {
lastSeen = value;
lastSeenTime = now;
return of(value);
}
})
)
.subscribe(observer);
});
};
}

Categories