About generators
Generators are a new kind of function that was introduced in ES6. Many articles have been written about them and many theoretical examples are given. As for me, the book You don't know JS , part of async & performance helped to clarify the essence of generators and how to use them . Of all the JS books I've studied, this one is the most packed with useful information without water.
Imagine that the generator (the function in the declaration, which is *) is some kind of electrical device with a remote control panel. After creating and mounting the generator (function declaration), you need to "spin" it (execute this function) so that it rotates at idle speed and "feeds" the control panel by itself (when the generator function is executed, it returns an iterator). This remote control has two buttons: Start (call the next method of the iterator for the first time) and Next (subsequent calls to the next method of the iterator). Then, with this control panel, you can rush around the entire power plant (according to our application) and when you need electrical energy (some values ββfrom the generator function), press the next button on the remote control (execute the next () method of the generator).The generator produces the required amount of electricity (returns a certain value through yield) and goes into idle mode again (the generator function waits for the next next call from the iterator). The loop continues as long as the generator can produce electricity (there are yield statements) or it will not stop (return is encountered in the generator function).
And in this whole analogy, the key point is the control panel (iterator). It can be passed to different parts of the application and at the right time "take" values ββfrom the generator. To complete the picture, you can add an unlimited number of buttons on the control panel to start the generator in certain modes (passing parameters to the next method (any parameters) of the iterator), but two buttons are enough to implement the hook.
Option 4. Generator without promises
This option is provided for clarity, because generators work in full force with promises (async / await mechanism). But this option is working and has the right to exist in certain simple situations.
I create a variable in the hook to store the reference to the iterator (cell for the generator control panel)
const iteratorRef = useRef(null);
. . , next() ( next). :
const updateCounter = () => {
iteratorRef.current.next();
};
const checkImageLoading = (url) => {
const imageChecker = new Image();
imageChecker.addEventListener("load", updateCounter);
imageChecker.addEventListener("error", updateCounter);
imageChecker.src = url;
};
. , , , next . , " ". dispatch , . :
function* main() {
for (let i = 0; i < imgArray.length; i++) {
checkImageLoading(imgArray[i].src);
}
for (let i = 0; i < imgArray.length; i++) {
yield true;
dispatch({
type: ACTIONS.SET_COUNTER,
data: stateRef.current.counter + stateRef.current.counterStep
});
}
}
"" , ( iteratorRef. ( next ).
.
import { useReducer, useEffect, useLayoutEffect, useRef } from "react";
import { reducer, initialState, ACTIONS } from "./state";
const PRELOADER_SELECTOR = ".preloader__wrapper";
const PRELOADER_COUNTER_SELECTOR = ".preloader__counter";
const usePreloader = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const stateRef = useRef(state);
const iteratorRef = useRef(null);
const preloaderEl = document.querySelector(PRELOADER_SELECTOR);
const counterEl = document.querySelector(PRELOADER_COUNTER_SELECTOR);
const updateCounter = () => {
iteratorRef.current.next();
};
const checkImageLoading = (url) => {
const imageChecker = new Image();
imageChecker.addEventListener("load", updateCounter);
imageChecker.addEventListener("error", updateCounter);
imageChecker.src = url;
};
useEffect(() => {
const imgArray = document.querySelectorAll("img");
if (imgArray.length > 0) {
dispatch({
type: ACTIONS.SET_COUNTER_STEP,
data: Math.floor(100 / imgArray.length) + 1
});
}
function* main() {
for (let i = 0; i < imgArray.length; i++) {
checkImageLoading(imgArray[i].src);
}
for (let i = 0; i < imgArray.length; i++) {
yield true;
dispatch({
type: ACTIONS.SET_COUNTER,
data: stateRef.current.counter + stateRef.current.counterStep
});
}
}
iteratorRef.current = main();
iteratorRef.current.next();
}, []);
useLayoutEffect(() => {
stateRef.current = state;
if (counterEl) {
stateRef.current.counter < 100
? (counterEl.innerHTML = `${stateRef.current.counter}%`)
: hidePreloader(preloaderEl);
}
}, [state]);
return;
};
const hidePreloader = (preloaderEl) => {
preloaderEl.remove();
};
export default usePreloader;
.
5.
. next ( ). ( ).
:
const getImageLoading = async function* (imagesArray) {
for (const img of imagesArray) {
yield new Promise((resolve, reject) => {
const imageChecker = new Image();
imageChecker.addEventListener("load", () => resolve(true));
imageChecker.addEventListener("error", () => resolve(true));
imageChecker.src = img.url;
});
}
};
:
for await (const response of getImageLoading(imgArray)) {
dispatch({
type: ACTIONS.SET_COUNTER,
data: stateRef.current.counter + stateRef.current.counterStep
});
}
for await ... of. Next.
- . , , .
import { useReducer, useEffect, useRef } from "react";
import { reducer, initialState, ACTIONS } from "./state";
const PRELOADER_SELECTOR = ".preloader__wrapper";
const PRELOADER_COUNTER_SELECTOR = ".preloader__counter";
const usePreloader = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const stateRef = useRef(state);
const preloaderEl = document.querySelector(PRELOADER_SELECTOR);
const counterEl = document.querySelector(PRELOADER_COUNTER_SELECTOR);
useEffect(() => {
async function imageLoading() {
const imgArray = document.querySelectorAll("img");
if (imgArray.length > 0) {
dispatch({
type: ACTIONS.SET_COUNTER_STEP,
data: Math.floor(100 / imgArray.length) + 1
});
for await (const response of getImageLoading(imgArray)) {
dispatch({
type: ACTIONS.SET_COUNTER,
data: stateRef.current.counter + stateRef.current.counterStep
});
}
}
}
imageLoading();
}, []);
useEffect(() => {
stateRef.current = state;
if (counterEl) {
stateRef.current.counter < 100
? (counterEl.innerHTML = `${stateRef.current.counter}%`)
: hidePreloader(preloaderEl);
}
}, [state]);
return;
};
const getImageLoading = async function* (imagesArray) {
for (const img of imagesArray) {
yield new Promise((resolve, reject) => {
const imageChecker = new Image();
imageChecker.addEventListener("load", () => resolve(true));
imageChecker.addEventListener("error", () => resolve(true));
imageChecker.src = img.url;
});
}
};
const hidePreloader = (preloaderEl) => {
preloaderEl.remove();
};
export default usePreloader;
:
:
useRef ( )
how to control the flow of events using generators, but without using promises (using callbacks)
how to manage the flow of events with promisified handlers using generators and a for await ... of loop
Sandbox link
Repository link
To be continued ... redux-saga ...