Code with Hugo: Polls, dates, types and styles
Polls, dates, types and styles
This week’s edition is a compilation of shorter tips and tricks:
- how to do HTTP polling with RxJS 5
- mocking
Date.now
in Jest tests - back to basics with an example of how to traverse a nested object
- CSS tip of the week
Better HTTP Polling with RxJS 5
Here is the implementation for a function that takes a Promise-based fetch function and a function that decides if a response has been successful (from a polling perspective) and polls the fetch function until the isSuccess
function returns true
:
import { Observable, from, interval } from 'rxjs';
import { switchMap, takeWhile } from 'rxjs/operators';
const POLLING_INTERVAL = 600; // in milliseconds
function poll(fetchFn, isSuccessFn, pollInterval = POLLING_INTERVAL) {
return interval(POLLING_INTERVAL).pipe(
switchMap(() => from(fetchFn())),
takeWhile((response) => isSuccessFn(response))
);
}
The above leverages RxJS 5 and uses its new pipe
syntax. If we walk through it:
- create an Observable that emits every 600ms (by default) using
interval
- pipe that into
switchMap
, this operator essentially replaces the Observable’s value to another one, in this instance, replaced the emitted interval count with an Observable created from the Promise returned byfetchFn
takeWhile
theisSuccessFn
returns true
To go into a bit more detail about switchMap
, it’s a bit like doing this in a Promise chain:
const promiseChain = Promise
.resolve()
.then(
() => someFunctionThatReturnsAPromise()
);
Anything that is now .then
-ed onto promiseChain
would now have the output of someFunctionThatReturnsAPromise
passed in.
Back to the poll function, this is an example of how it would be used:
import axios from 'axios;
poll(
axios.get('https://changing-status.com/status'),
(response) => response.data.status === 'success'
).subscribe((response) => {
// response is HTTP response
console.log(response.data.status);
});
The advantage of this Observable-based approach is that we have access to each HTTP response. That’s nice if we need to display the status somewhere. An Observables are also pretty composable, so to turn what we’ve got into something that just emits once when the poll completes we can also do that:
import { skipWhile } from 'rxjs/operators';
const emitOnPollComplete = poll(
axios.get('https://changing-status.com/status'),
(response) => response.data.status === 'success'
).pipe(
skipWhile((response) => response.data.status !== 'success')
).subscribe((response) => {
console.log('Poll complete');
});
Mocking the current Date in Jest tests
There are situations where Date.now
is used in application code. That code needs to be tested, and it’s always a struggle to remember how to mock Date.now
. Here is the magic snippet:
const literallyJustDateNow = () => Date.now();
test('It should call and return Date.now()', () => {
const realDateNow = Date.now.bind(global.Date);
const dateNowStub = jest.fn(() => 1530518207007);
global.Date.now = dateNowStub;
expect(literallyJustDateNow()).toBe(1530518207007);
expect(dateNowStub).toHaveBeenCalled();
global.Date.now = realDateNow;
});
This isn’t really a Jest-specific trick, we’re just accessing Node global
object and replace Date.now
with a stub.
We’re also being good unit-testing citizens and putting the original global.Date.now
implementation back 😇.
Detecting Object vs Array by example
Let’s say we have the following type of object and we want to measure its maximum depth:
const obj = {
myKey: {
nest: {
doubleNested: 'value',
nestedArray: [ { key: 'value' } ]
}
}
};
We should be able to do this with the following:
function maxDepth(obj, depth = 0) {
if (typeof obj !== 'object') {
return depth;
}
const [values, depthIncrease] = Array.isArray(obj)
? [obj, 0]
: [Object.values(obj), 1];
return values.length > 0
? Math.max(...values.map(
value => maxDepth(value, depth + depthIncrease))
)
: depth;
}
// Some of these fail even though
// the assertions hold 🙄
console.assert(maxDepth({}), 0);
console.assert(maxDepth(''), 0);
console.assert(maxDepth([ { one: 'deep' } ]), 1);
console.assert(maxDepth({ one: 'deep' }), 1);
console.assert(maxDepth({ one: [ { two: 'deep' } ] }), 2)
console.assert(maxDepth({ one: { two: 'deep' } }), 2)
To break down object vs primitive type detection, it’s a case of typeof obj === 'object'
, see this quick reminder of types of things:
console.assert(typeof '', 'string');
console.assert(typeof new String(), 'string');
console.assert(typeof 1, 'number');
console.assert(typeof Infinity, 'number');
console.assert(typeof NaN, 'number');
console.assert(typeof undefined, 'undefined');
console.assert(typeof [], 'object');
console.assert(typeof null, 'object');
console.assert(typeof {}, 'object');
console.assert(typeof new Map(), 'object');
console.assert(typeof new Set(), 'object');
Now to separate Objects vs Arrays it’s Array.isArray
every day, although we could use a check on .length
, there’s also the caveat of Set
or Map
being passed around the function but thankfully they have a .size
property instead of length
:
// Console.assert flips out again
// even though the assertions hold
console.assert(Array.isArray({}), false);
console.assert(Array.isArray(new Map()), false);
console.assert(Array.isArray(new Set()), false);
console.assert(Array.isArray([]), true);
console.assert(Array.isArray(new Array()), true);
We could also use .length > 0
, although that will check for a non-empty Array, or .length != null
, and that’s a great use case for !=
/==
, but let’s stay away from that lest someone changes it to a !==
/===
.
CSS tip: object-fit
your images
I discovered this week that instead of doing something like:
.thumbnail {
width: 50px;
height: 50px;
background-size: cover;
background-position: center;
}
With this associated HTML (yes I know inline styles… 👎):
<div
class="thumbnail"
style="background-image: url('some-url');"
>
</div>
We can do:
.thumbnail {
width: 50px;
height: 50px;
object-fit: cover;
object-position: center;
}
With the following HTML:
<img class="thumbnail" src="some-url">
Why is this cool?
- One word: accessibility.
- Two words: semantic markup.
- Many words: with the
div
+background-image
solution, if the URL turned out to be broken, it would show empty space, with theimg
+object-fit
one, it shows the good old “broken image because of URL” fallback (oralt
attribute value). Animg
tag is more accessible since we can havealt
, and finally typingsrc="my-url"
is just less characters thanstyle="background-image: url('my-url')"
.
Warning: this might not work on older browsers 🤔.