TIL use-cases for Generators in Javascript
POSTED ON:
TAGS: advanced javascript mdn
This is a generator.
The function* declaration (function keyword followed by an asterisk) defines a generator function, which returns a Generator object.
via the MDN
function* greeter() {
yield 'Hi';
yield 'How are you?';
yield 'Bye';
}
const greet = greeter();
console.log(greet.next().value);
// 'Hi'
console.log(greet.next().value);
// 'How are you?'
console.log(greet.next().value);
// 'Bye'
console.log(greet.next().value);
// undefined
How does it work?
-
Generators are functions that can be exited and later re-entered. Their context (variable bindings) will be saved across re-entrances.
-
It has a iterator, which saves a count. It then executes code until the
yield
expression. -
You can either have a finite number of yields, after which
next()
returns an undefined value, or an infinite number of values using a loop. -
It's considered a Lazy Evaluation, as it only calculates on demand. Makes it very memory efficicent.
Use Cases
via Use-Cases For JavaScript Generators
1 - Throttling a function
export function * throttle(func, time) {
let timerID = null;
function throttled(arg) {
clearTimeout(timerID);
timerID = setTimeout(func.bind(window, arg), time);
}
while(true) throttled(yield);
}
export class GeneratorThrottle {
constuctor() {};
start = () => {
thr = throttle(console.log, 3000);
thr.next('');
};
toString = () => {
console.log(throttle);
console.log('start =', this.start);
};
};
A state machine:
export class ContentStateMachine {
_content;
_default;
_statePatterns;
_returnState;
_changeAlgorithm;
_machine;
constructor(settings) {
this._content = settings.content;
this._default = settings.defaultIndex;
this._statePatterns = settings.statePatterns;
this._returnState = settings.returnState;
this._changeAlgorithm = settings.changeAlgorithm;
const machineSettings = {
'content': this._content,
'defaultIndex': this._default,
'statePatterns': this._statePatterns,
'returnState': this._returnState
};
this._machine = this.stateMachine(machineSettings);
return this._machine;
};
stateMachine = function * stateMachine(settings) {
const content = settings.content;
const defaultIndex = settings.defaultIndex;
const statePatterns = settings.statePatterns;
const returnState = settings.returnState;
let currentIndex = defaultIndex;
while (currentIndex >= 0 && currentIndex < content.length) {
if (this._changeAlgorithm) {
const states = returnState(content, currentIndex);
this._changeAlgorithm(states, currentIndex);
}
const changeType = yield returnState(content, currentIndex);
currentIndex = statePatterns[changeType](content, currentIndex);
}
};
}
Some more use-cases:
- for loops which need to be paused and resumed at a later date
- infinitely looping over an array and having it reset to the beginning once it's done
- creating iterables to use in for of loops from non-iterable objects using [Symbol.Iterator]
- You'd want to play and pause loops if someone hit a pause button on an animation and the loop is a tween.
- You'd want an infinitely looping array for things like carousels or lists of data which wrap around
- You'd want to iterate over objects to check if any of its properties include a specific value. You get the array and then check with .includes()
- You'd want a map if you have objects already and want to keep the object like structure, but also want to check if properties exist on an object that are falsy.
Via EmNudge
I had to find all people who have commented on issues at a particular GitHub repo. GitHub's rest API provides us with an endpoint that can list issue comments. With each page having a few results, in order to find all the commentors, we have to traverse all the pages. Now we can do this in a single run, and then show user the results, which will take really really long time of user seeing nothing (on a repo with 250 pages, it took 5-6 min). What can be better for user experience is to keep emitting unique commentors as we find them, hence creating a streamed output so user knows things are really in progress and not broken.
via Sid Vishnoi
The original reference: A Collection of JavaScript Tips Based on Common Areas of Confusion or Misunderstanding
and this:
// promise-based fetch
function fetchJson(url) {
return fetch(url)
.then(request => request.text())
.then(text => {
return JSON.parse(text);
})
.catch(error => {
console.log(`ERROR: ${error.stack}`);
});
}
// one with a generator and the co library
// https://github.com/tj/co
const fetchJson = co.wrap(function* (url) {
try {
let request = yield fetch(url);
let text = yield request.text();
return JSON.parse(text);
}
catch (error) {
console.log(`ERROR: ${error.stack}`);
}
});
via Exploring ES6
Related TILs
Tagged: advanced