All the codes have been included in the async-utilities repository, and has been published to npm
Background
In the HTML
form, there is a scenario like this:
<form id="form">
<!-- <label for="name">Nameďź</label>
<input type="text" name="name" id="name"> -->
<button type="submit">submit</button>
</form>
Clicking âSubmitâ will send a request to the server:
// make a network request
function api(data) {
console.log("submiting", data);
return fetch("https://httpbin.org/delay/1.5", {
body: JSON.stringify(data),
method: "POST",
mode: "cors",
});
}
const form = document.getElementById("form");
const handler = async function (e) {
e.preventDefault();
const rez = await someApi({
msg: "some data to be sent",
});
console.log(rez);
};
form.addEventListener("submit", handler);
To prevent users from submitting repeatedly, we usually maintain a loading
state⌠but after writing it many times, there is inevitably a feeling of mechanical labor. Moreover, when a form has many buttons, wouldnât I have to maintain many loading
variables?
Looking at this makes my eyes tired and the interface response is very fast; sneaking in one less loading
probably wonât be noticed rightđ, but what if the server goes down⌠never mind, no time to think about that now.
Have you ever experienced the scenario above? In fact, most productsâ buttons do not have a loading
effect because the whole world is just a big slapdash operationđ. However, as a qualified front-end developer, everyone needs to be responsible for user experience!
Can we just make money standing tall?
Letâs sort it out first:
-
Within a short period of time, each event will generate a
promise
, and the core requirement is to reduce frequency. That is, âfor three thousandpromises
, I only take one result.â -
The response time of a
promise
is uncertain.
Frequency reduction
Recall the event frequency reduction in synchronous code: throttle
and debounce
. Regarding these two, I believe you are already very familiar with them; letâs summarize in one sentence:
Both are taking one call from multiple same events within a unit of time (also can be said as: for three thousand events, I only execute once); the difference is that the former takes the first occurrence while the latter takes the last.
Letâs rephrase our requirement in this style: taking one call from multiple same events within a short period of time. So, this âshort period of timeâ is key!
Interval redefinition
We hope that before the previous promise
ends, all subsequent operations to create new promises
are discarded. Therefore, âwithin a short period of timeâ equals âduring the pending period of the previous promise
â, and discarding all subsequent promise
creation operations means âtaking the first occurrenceâ. The discarding of promise
can be achieved by creating a promise
that is âforever pending
â; thus our requirement becomes:
During the pending period of the previous promise
, take only the first operation out of multiple attempts to create new promises
(which would be this currently pending promise
) for execution.
Coding
Now that weâve discussed ideas letâs also refer to some code, hereâs a simple version implementation code for throttling
:
/**
* @description throttling
* @param {function} fn
* @param {number} ms milliseconds
* @returns {function} Throttled Function
*/
function throttle(fn, ms = 300) {
let lastInvoke = 0;
return function throttled(...args) {
const now = Date.now();
if (now - lastInvoke < ms) return;
lastInvoke = now;
fn.call(this, ...args);
};
}
Imitate the gourd to draw a ladle, simply modify it a bit:
/**
* @description Asynchronous throttling: During the last promise pending period, it will not be triggered again
* @param {() => Promise<any>} fn
* @returns {() => Promise<any>} Throttled Function
*/
function throttleAsyncResult(fn) {
let isPending = false;
return function (...args) {
if (isPending) return new Promise(() => {});
isPending = true;
return fn
.call(this, ...args)
.then((...args1) => {
isPending = false;
return Promise.resolve(...args1);
})
.catch((...args2) => {
isPending = false;
return Promise.reject(...args2);
});
};
}
Usage(Demo)
The following demo takes a network request as an example and opens Devtool to see the effect.
View Source Code
import { throttleAsyncResult } from "@bowencool/async-utilities";
/* make a network request */
function api(data: { msg: string }) {
console.log("submiting", data);
return fetch("https://httpbin.org/delay/1.5", {
body: JSON.stringify(data),
method: "POST",
mode: "cors",
});
}
const throttledApi = throttleAsyncResult(api);
export default function ThrottleAsyncResultDemo() {
return (
<button
onClick={async function () {
const rez = await throttledApi({
msg: "some data to be sent",
});
console.log("completed");
}}
>
submit(click me quickly)
</button>
);
}
When you open the developer tools, you can see that no matter how fast you click, there will never be a situation where requests are made in parallel:
Mission accomplished!
A twin brother
debounceAsyncResult
The throttleAsyncResult
just now is about controlling how to create a promise
. So, if we have already created many promises
, how can we get the latest result? After all, nobody knows which promise
will run faster.
Therefore, there is debounceAsyncResult
(Demo): Among the many created promises, take the result of the last created promise.
âBeing lazyâ is the primary productive force for programmers, have you learned itđ¤?