提交学习笔记专用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

88 lines
2.4 KiB

  1. //#region src/index.ts
  2. const DEBOUNCE_DEFAULTS = { trailing: true };
  3. /**
  4. Debounce functions
  5. @param fn - Promise-returning/async function to debounce.
  6. @param wait - Milliseconds to wait before calling `fn`. Default value is 25ms
  7. @returns A function that delays calling `fn` until after `wait` milliseconds have elapsed since the last time it was called.
  8. @example
  9. ```
  10. import { debounce } from 'perfect-debounce';
  11. const expensiveCall = async input => input;
  12. const debouncedFn = debounce(expensiveCall, 200);
  13. for (const number of [1, 2, 3]) {
  14. console.log(await debouncedFn(number));
  15. }
  16. //=> 1
  17. //=> 2
  18. //=> 3
  19. ```
  20. */
  21. function debounce(fn, wait = 25, options = {}) {
  22. options = {
  23. ...DEBOUNCE_DEFAULTS,
  24. ...options
  25. };
  26. if (!Number.isFinite(wait)) throw new TypeError("Expected `wait` to be a finite number");
  27. let leadingValue;
  28. let timeout;
  29. let resolveList = [];
  30. let currentPromise;
  31. let trailingArgs;
  32. const applyFn = (_this, args) => {
  33. currentPromise = _applyPromised(fn, _this, args);
  34. currentPromise.finally(() => {
  35. currentPromise = null;
  36. if (options.trailing && trailingArgs && !timeout) {
  37. const promise = applyFn(_this, trailingArgs);
  38. trailingArgs = null;
  39. return promise;
  40. }
  41. });
  42. return currentPromise;
  43. };
  44. const debounced = function(...args) {
  45. if (options.trailing) trailingArgs = args;
  46. if (currentPromise) return currentPromise;
  47. return new Promise((resolve) => {
  48. const shouldCallNow = !timeout && options.leading;
  49. clearTimeout(timeout);
  50. timeout = setTimeout(() => {
  51. timeout = null;
  52. const promise = options.leading ? leadingValue : applyFn(this, args);
  53. trailingArgs = null;
  54. for (const _resolve of resolveList) _resolve(promise);
  55. resolveList = [];
  56. }, wait);
  57. if (shouldCallNow) {
  58. leadingValue = applyFn(this, args);
  59. resolve(leadingValue);
  60. } else resolveList.push(resolve);
  61. });
  62. };
  63. const _clearTimeout = (timer) => {
  64. if (timer) {
  65. clearTimeout(timer);
  66. timeout = null;
  67. }
  68. };
  69. debounced.isPending = () => !!timeout;
  70. debounced.cancel = () => {
  71. _clearTimeout(timeout);
  72. resolveList = [];
  73. trailingArgs = null;
  74. };
  75. debounced.flush = () => {
  76. _clearTimeout(timeout);
  77. if (!trailingArgs || currentPromise) return;
  78. const args = trailingArgs;
  79. trailingArgs = null;
  80. return applyFn(this, args);
  81. };
  82. return debounced;
  83. }
  84. async function _applyPromised(fn, _this, args) {
  85. return await fn.apply(_this, args);
  86. }
  87. //#endregion
  88. export { debounce };