misc.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. import type { Stringifiable } from "./types.js";
  2. /**
  3. * Automatically appends an `s` to the passed {@linkcode word}, if {@linkcode num} is not equal to 1
  4. * @param word A word in singular form, to auto-convert to plural
  5. * @param num If this is an array or NodeList, the amount of items is used
  6. */
  7. export function autoPlural(word: Stringifiable, num: number | unknown[] | NodeList): string {
  8. if(Array.isArray(num) || num instanceof NodeList)
  9. num = num.length;
  10. return `${word}${num === 1 ? "" : "s"}`;
  11. }
  12. /**
  13. * Inserts the passed values into a string at the respective placeholders.
  14. * The placeholder format is `%n`, where `n` is the 1-indexed argument number.
  15. * @param input The string to insert the values into
  16. * @param values The values to insert, in order, starting at `%1`
  17. */
  18. export function insertValues(input: string, ...values: Stringifiable[]): string {
  19. return input.replace(/%\d/gm, (match) => {
  20. const argIndex = Number(match.substring(1)) - 1;
  21. return (values[argIndex] ?? match)?.toString();
  22. });
  23. }
  24. /** Pauses async execution for the specified time in ms */
  25. export function pauseFor(time: number): Promise<void> {
  26. return new Promise<void>((res) => {
  27. setTimeout(() => res(), time);
  28. });
  29. }
  30. /**
  31. * Calls the passed {@linkcode func} after the specified {@linkcode timeout} in ms (defaults to 300).
  32. * Any subsequent calls to this function will reset the timer and discard all previous calls.
  33. * @param func The function to call after the timeout
  34. * @param timeout The time in ms to wait before calling the function
  35. * @param edge Whether to call the function at the very first call ("rising" edge) or the very last call ("falling" edge, default)
  36. */
  37. export function debounce<
  38. TFunc extends (...args: TArgs[]) => void, // eslint-disable-next-line @typescript-eslint/no-explicit-any
  39. TArgs = any,
  40. > (
  41. func: TFunc,
  42. timeout = 300,
  43. edge: "rising" | "falling" = "falling"
  44. ): (...args: TArgs[]) => void {
  45. let timer: NodeJS.Timeout | undefined;
  46. return function(...args: TArgs[]) {
  47. if(edge === "rising") {
  48. if(!timer) {
  49. func.apply(this, args);
  50. timer = setTimeout(() => timer = undefined, timeout);
  51. }
  52. }
  53. else {
  54. clearTimeout(timer);
  55. timer = setTimeout(() => func.apply(this, args), timeout);
  56. }
  57. };
  58. }
  59. /** Options for the `fetchAdvanced()` function */
  60. export type FetchAdvancedOpts = Omit<
  61. RequestInit & Partial<{
  62. /** Timeout in milliseconds after which the fetch call will be canceled with an AbortController signal */
  63. timeout: number;
  64. }>,
  65. "signal"
  66. >;
  67. /** Calls the fetch API with special options like a timeout */
  68. export async function fetchAdvanced(input: RequestInfo | URL, options: FetchAdvancedOpts = {}): Promise<Response> {
  69. const { timeout = 10000 } = options;
  70. let signalOpts: Partial<RequestInit> = {},
  71. id: NodeJS.Timeout | undefined = undefined;
  72. if(timeout >= 0) {
  73. const controller = new AbortController();
  74. id = setTimeout(() => controller.abort(), timeout);
  75. signalOpts = { signal: controller.signal };
  76. }
  77. try {
  78. const res = await fetch(input, {
  79. ...options,
  80. ...signalOpts,
  81. });
  82. id && clearTimeout(id);
  83. return res;
  84. }
  85. catch(err) {
  86. id && clearTimeout(id);
  87. throw err;
  88. }
  89. }