Is an observable just a partially applied function? - javascript

For clarity, this question applies to the observable pattern implemented in javascript (the code I'll write here is all ES6).
Recently I've been trying to figure out how observables actually work. I found a couple articles/posts that describe them within the context of creating a class that takes a function as its constructor argument. This function just waits around for an observer, and the method .subscribe(observer) finally triggers this function with the observer as argument.
Here is my attempt at creating an observable with class syntax that implements an interval with a limit on the number of interval ticks (so that there is something to complete with):
class Observable {
constructor(pushFn) {
this.pushFn = pushFn; // waits for observer as argument
}
static interval(milliSecs, tickLimit) {
return new Observable(observer => {
if (milliSecs <= 0) {
observer.error("Invalid interval. Interval value must be larger than 0.")
} else {
let tick = 0;
const inter = setInterval(() => {
observer.next(tick += 1);
if (tick === tickLimit) {
clearInterval(inter);
observer.complete(tickLimit);
}
}, milliSecs);
}
});
}
subscribe(observer) {
return this.pushFn(observer);
}
}
What I find kind of surprising (given that I always thought of observables as some sort mysterious super complex thing) is that this functionality can be replicated with the following function:
const observableIntervalOf = (milliSecs, tickLimit) => observer => {
if (milliSecs <= 0) {
observer.error("Invalid interval. Interval value must be larger than 0.")
} else {
let tick = 0;
const inter = setInterval(() => {
observer.next(tick += 1);
if (tick === tickLimit) {
clearInterval(inter);
observer.complete(tickLimit);
}
}, milliSecs);
}
};
Then, the effect of each of the following is identical:
Observable.interval(1000, 5).subscribe(someObserver);
const fiveSeconds = observableIntervalOf(1000, 5);
fiveSeconds(someObserver);
Am I right in assuming that its best to think of observables as partially applied functions? Are there instances where this is not the case?

Related

how to use variable immediately after it's defined - js

How, sorry for this weird title, i didn't know how to put this... Here my explanation:
I have a function where I define variable (I'm looping over an array and if condition is matched, I define let). It works everytime item.dataset.category changes, so after every scroll.
After the loop, I call another function, where I use this variable as an argument. In the second function I use it to check if another condition is matched:
//first function
const getDataset = (e) => {
let dataset;
const cat = Array.from(categories.children);
cat.forEach((item) => {
if (item.className.includes('active')) {
dataset = item.dataset.category;
}
});
changeNavActive(dataset);
};
//second function
const changeNavActive = (dataset) => {
const navItems = Array.from(navList.children);
navItems.forEach((item) => {
item.classList.remove('active');
if (item.dataset.category === dataset) {
item.classList.add('active');
}
});
};
It's not working and I think I understand why - callign of second function is at the same time as declaring variable, so I geting this let in the next call. The result is that second function works with delay of one scroll.
This is a function which calls getDataset():
const scrollRows = (e) => {
if (window.scrollY > slider.clientHeight) {
e.deltaY > 0 ? move++ : move--;
getDataset();
if (move > categories.children.length - 1)
move = categories.children.length - 1;
}
}
How to fix this?

How to update state using settimeout function inside loop in react?

I'm creating react app to visualize sorting arhorithms and I stopped on this problem. I'm looping through all elements of array from bars state and I want to swap them (for test purposes). This is not working excatly as I wanted, because it "ignores" setTimeout function and does it immediately. I tried something with setBars but it is not working too. How can I make this so swapping will happen after timeout set in setTimeout function?
const [bars, setBars] = useState([23, 63, 236, 17, 2]);
const swap = (arr, i, j) => {
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
};
for (let i = 0; i < bars.length - 1; i++) {
setTimeout(() => {
swap(bars, i, i + 1);
}, 2000);
}
You'd want to use a useEffect hook to start the process when the component mounts:
useEffect(() => {
// ...code here...
}, []);
The empty dependencies array at the end says "only run this when the component first mounts."
Within it, your code would schedule the timer callbacks:
for (let i = 0; i < bars.length - 1; i++) {
setTimeout(() => {
setBars(bars => {
// Copy the current array
const newBars = [...bars];
// Do the swap
swap(newBars, i, i + 1);
// Set the state by returning the update
return newBars;
});
}, 2000 * (i + 1));
}
Note that that uses the callback form of setBars, so that you're operating on the then-current state (instead of bars, which is only the array used during the first mount, since your function is only called when that first mount occurs).
Also note that the interval is 2000 * (i + 1) rather than just 2000. That's so each callback occurs 2000ms after the last one, since they're all being scheduled at once.
Another important aspect of the above is that it uses let within the for, so each loop body gets its own i variable. (If the code used var, or let outside the for, all the callbacks would share the same i variable, which would have the value bars.length.)
Alternatively, I think I might take this approach:
Since you want to update both i and bars at the same time and since it's best to use the callback form of the state setter when doing updates asynchronously (as with setTimeout), I'd combine i and bars into a single state item (whereas I'd normally keep them separate):
const [barState, setBarState] = useState({i: 0, bars: [23, 63, 236, 17, 2]});
Then you'd use a useEffect hook to kick the process off when your component mounts and then do it again after each change to barState:
useEffect(() => {
// ...code here...
}, [barState]);
The dependencies array containing barState at the end says "run this on component mount and every time barState changes."
Within it, your code would schedule the timer callbacks:
if (barState.i < barState.bars.length - 1) {
setTimeout(() => {
setBarState(({i, bars}) => {
// Copy the current array
bars = [...bars];
// Do the swap
swap(bars, i, i + 1);
// Set the state by returning the update
++i;
return {i, bars};
});
}, 2000);
}
Live Example:
const { useState, useEffect } = React;
const swap = (arr, i, j) => {
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
};
function Example() {
const [barState, setBarState] = useState({i: 0, bars: [23, 63, 236, 17, 2]});
useEffect(() => {
if (barState.i < barState.bars.length - 1) {
setTimeout(() => {
setBarState(({i, bars}) => {
// Copy the current array
bars = [...bars];
// Do the swap
swap(bars, i, i + 1);
// Set the state by returning the update
++i;
return {i, bars};
});
}, 2000);
}
}, [barState]);
return <div>{barState.bars.join(", ")}</div>;
}
ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>

When boolean turns false, check 5 seconds if it will turn back to true, else do some action

I have in NodeJS a variable that updates every second. I want to monitor it to see if it turns below a certain threshold (e.g. 1000).
If it does go below the threshold, it should wait 5 seconds and monitor if the variable goes back above again. If not, it should return a function. If it does go above, it can stop the times and start monitoring again.
Can't get any code to work.
Not sure if the code below is even in the right direction..!
var waitedSoFar = 0;
var imageDisplayed = CheckIfImageIsDisplayed(); //this function is where you check the condition
while(waitedSoFar < 5000)
{
imageDisplayed = CheckIfImageIsDisplayed();
if(imageDisplayed)
{
//success here
break;
}
waitedSoFar += 100;
Thread.Sleep(100);
}
if(!imageDisplayed)
{
//failed, do something here about that.
}
You could have a function that observe and wrap this variable that change so often. Something as simple as
function observeValue(initialValue) {
let value = initialValue
let clearEffects = []
let subscribers = []
return {
value: () => value,
change(val) {
clearEffects.forEach(oldEffect => oldEffect && oldEffect())
value = val
clearEffects = subscribers.map(subscriber => subscriber(value))
},
subscribe(listener) {
subscribers.push(listener)
}
}
}
is generic enough. The function returns three methods:
one to get the current value
one to change the value
on to subscribe on every value change
I would suggest to leverage the last one to monitor and trigger any side effects.
You can use it like this:
const monitor = observeValue(true)
monitor.subscribe((value) => {
if (value !== false) {
return
}
const timeout = setTimeout(() => {
console.log('value was false for five seconds')
}, 5000)
return () => {
clearTimeout(timeout)
}
})
and somewhere else you can change the value with
monitor.change(false)

Same intersection observer for multiple HTML elements

I am trying to make it so that as some text items stop overlapping a dark background, they will individually change color one by one as the user scrolls. All of the text items are position: fixed
EDIT: The MDN docs say (emphasis mine):
The Intersection Observer API provides a way to asynchronously observe
changes in the intersection of a target element with an ancestor
element
I think this means there is no way to solve my problem because the elements I want to monitor for overlap are not children of the root I am specifying in the options object.
Is there any way to detect overlap if the overlapping element is not a child of the other element?
if ("IntersectionObserver" in window) {
const options = {
root: document.getElementById("flow-landing"),
rootMargin: "0px",
threshold: 0,
};
var callback = function (entries, observer) {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.style.color = "white";
} else {
entry.target.style.color = null;
}
});
};
const observer = new IntersectionObserver(callback, options);
var targets = [
Array.from(document.querySelectorAll(".social-item")),
Array.from(document.querySelectorAll(".additional-item")),
].flat();
targets.forEach((target) => observer.observe(target));
}
There aren't any console errors but the code isn't doing anything.
Modifying Ruslan's answer a little because in his answer, multiple Intersection Observer objects are being created.
It is possible to observe multiple elements using the same observer by calling .observe() on multiple elements.
let observerOptions = {
rootMargin: '0px',
threshold: 0.5
}
var observer = new IntersectionObserver(observerCallback, observerOptions);
function observerCallback(entries, observer) {
entries.forEach(entry => {
if(entry.isIntersecting) {
//do something
}
});
};
let target = '.targetSelector';
document.querySelectorAll(target).forEach((i) => {
if (i) {
observer.observe(i);
}
});
you can do something like that, at least it helps me:
document.querySelectorAll('.social-item').forEach((i) => {
if (i) {
const observer = new IntersectionObserver((entries) => {
observerCallback(entries, observer, i)
},
{threshold: 1});
observer.observe(i);
}
})
const observerCallback = (entries, observer, header) => {
entries.forEach((entry, i) => {
if (entry.isIntersecting) {
entry.target.style.color = "white";
}
else {
entry.target.style.color = null;
}
});
};
You could use the offsetTop and offsetHeight properties instead of the IntersectionObserver API.
For example, when the user scrolls, you can check to see if the offsetTop of element 2 is greater than the offsetTop and less than the offsetHeight of element 1.
WARNING: use debounce because the scroll event's handler function will be called once for every pixel the user scrolls, just imagine the performance nightmare that would occur if user scrolled 600-1000 pixels.
The LoDash documentation, describes it's debounce function as:
"[a function that] creates a debounced function that delays invoking func (handler) until after wait (time) milliseconds have elapsed since the last time the debounced function was invoked."
If you aren't using LoDash, here is a Vanilla JavaScript debounce function:
function debounce(handler, time) {
var timeout;
return function() {
var self = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function() {
return handler.apply(self, args);
}, time);
};
}
Here is the code that'll allow you to "do stuff" if element 2 "intersects" element 1.
let element_1 = document.querySelector("#my-element-1");
let element_2 = document.querySelector("#my-element-2");
window.addEventListener("scroll", debounce(() => {
if(element_2.offsetTop > element_1.offsetTop && element_2.offsetTop < element_1.offsetHeight) {
console.log("The elements are intersecting.");
}
}, 100));
In case that looks complex or hard to read, here is the same code, broken up into smaller chunks:
let element_1 = document.querySelector("#my-element-1");
let element_2 = document.querySelector("#my-element-2");
window.addEventListener("scroll", debounce(() => {
let x = element_2.offsetTop > element_1.offsetTop;
let y = element_2.offsetTop < element_1.offsetHeight;
if(x && y) {
console.log("The elements are intersecting.");
}
}, 250));
Notes & information:
you could use the >= and <= operators instead of the > and < operators
a wait time that's too long could make the effect look unnatural and forced.
Good luck.

Controller-independent stepper functions

I have an AngularJS project, and I'm using a modified version of md-steppers, whose interesting functions boil down to this:
var enableNextStep = function () {
//do not exceed into max step
if ($scope.selectedStep >= $scope.maxStep) {
return;
}
//do not increment $scope.stepProgress when submitting from previously completed step
if ($scope.selectedStep === $scope.stepProgress - 1) {
$scope.stepProgress = $scope.stepProgress + 1;
}
};
var completeCurrentStep = function (CurrentStep) {
$scope.stepData[CurrentStep].completed = true;
};
$scope.moveToNextStep = function moveToNextStep() {
if ($scope.selectedStep < $scope.maxStep) {
enableNextStep();
$scope.selectedStep = $scope.selectedStep + 1;
completeCurrentStep($scope.selectedStep - 1); //Complete After changing Step
}
};
$scope.moveToPreviousStep = function moveToPreviousStep() {
if ($scope.selectedStep > 0) {
$scope.selectedStep = $scope.selectedStep - 1;
}
};
The problem is that I would like to use these four functions in two different controllers (so as to not repeat them), that have different stepProgress, selectedStep and maxStep values. I couldn't find a way to do so with services, but I might just be confused about the way AngularJS work, as I am more used to Python.
Thanks.
Abstracting out that functionality into a factory that accepts an array of callbacks and a controller's ng-model would make it more reusable. Of course, ultimately the API you want is up to you. The goal is that you don't want any $scope business inside the factory, it shouldn't be concerned about what's inside the callbacks, it just steps through them.
/**
* #param steps {array} - array of callbacks
*/
function stepperFactory(steps) {
iterate(0, steps);
}
function iterate(current, steps) {
if (!steps[current])
return;
if (typeof steps[current] === 'function')
// pass an async "done" callback
// so your array of input callbacks can be async
// you could also use promises or $q for this
steps[current](() => iterate(current + 1, steps));
}
And so the api you expose would be like:
['stepperFactory', function(stepperFactory) {
this.model = { step: 0, msg: 'start' };
this.steps = [
(done) => {
this.model.step++;
done();
},
(done) => {
setTimeout(() => {
this.model.msg = '3rd step';
this.model.step++;
done();
});
}
];
stepperFactory(this.model, this.steps);
}]
You can use service to share functions which will take maxStep, stepProgress, etc as arguments and instead of modifying the $scope, they will return updated values.
In service:
function moveToPreviousStep(step) {
if (step > 0) {
return (step - 1);
}
return step;
};
and in controller
function moveToPreviousStep() {
$scope.selectedStep = service.moveToPreviousStep($scope.selectedStep);
}
$scope.moveToPreviousStep = moveToPreviousStep;

Categories

Resources