Source

function.js

//#region Function
/**
 * @callback juxtCallback
 * @param {...any} args The arguments to run on the functions
 * @returns {Array.<array>} The list of outputs.
 */
/**
 * Runs a list of functions with a list of arguments.
 * @returns {juxtCallback} The function to run with the args.
 * @memberOf function
 * @function
 * @example
 * //It returns an array of outputs, each item in the base array is the output of one function, and each item in that array is the output for each argument.
 * _$.juxt(
    x => x + 1,
    x => x - 1,
    x => x * 10
  )(1, 2, 3); // [[2, 3, 4], [0, 1, 2], [10, 20, 30]]
 * @param  {...function} fns The functions to call.
 */
export let juxt =
	(...fns) =>
	(...args) =>
		[...fns].map((fn) => [...args].map(fn));
/**
 * Returns a promise after a specified number of milliseconds.
 * @returns {Promise}
 * @memberOf function
 * @function
 * @example
 * (async () => {
 *    while (true){
 *     document.body.innerHTML = (await _$.getJSON("https://time.jsontest.com")).time
 *     await _$.sleep(60000);//Wait one minute then loop.
 *    }
 * })
 * @param {Number} ms The milliseconds to sleep.
 */
export let sleep = (ms = req("number", "milliseconds")) =>
	new Promise((resolve) => setTimeout(resolve, ms));

/**
 * Limits the arguments that a given function takes to only the 1st n arguments.
 * @example
 * //Now console can only log one item. How utterly useless but explanatory at the same time!
 * console.log = _$.limitArgs(console.log, 1);
 * @memberOf function
 * @function
 * @returns {Function} The new function that only takes the 1st n arguments.
 * @param {Function} fn The function to call.
 * @param {Number} n The number of arguments to accept.
 */
export let limitArgs =
	(
		fn = req("function", "function"),
		n = req("number", "arguments"),
	) =>
	(...args) =>
		fn(...args.slice(0, n));
/**
 * Returns the index of the fastest function in an array of functions.
 * @memberOf function
 * @function
 * @returns {Number} The index of the fastest function in the array.
 * @example
 * _$.fastestFunction([_$.uuid, () => _$.syntaxHighlight("<h1>Hello world</h1>", "html")]);//0, the first function.
 * @param {Array.<Function>} fns The array of functions to execute.
 * @param {Number} [iterations=1000] How many times to execute the functions. (More is more reliable but takes longer.)
 */
export let fastestFunction = (fns, iterations = 1000) => {
	const times = fns.map((fn) => {
		const before = performance.now();
		for (let i = 0; i < iterations; i++) fn();
		return performance.now() - before;
	});
	return times.indexOf(Math.min(...times));
};

/**
 * @callback spreadCallback
 * @param {Array} args The array of arguments
 * @returns {any}
 */
/**
 * Uses an array of arguments to make a function based on the one inputted.
 * @memberOf function
 * @function
 * @returns {spreadCallback}
 * @example
 * var say = _$.spread(function(who, what) {
    return who + ' says ' + what;
  });
  say(["Fred", "hi"]);//"Fred says hi"
 * @param {Function} fn The function to use
 */
export let spread = (fn = req("function")) => {
	return (args) => {
		fn.apply(globalThis, args);
	};
};
/**
 * Memoizes a function, basically caching the result of past operations so that if the exact same thing is called again it will return the same value instantly.
 * @function
 * @memberOf function
 * @param {Function} fn The function to memoize.
 * @example
 * let uuid = _$.memoize(() => _$.uuid()); // uuid will always return the same uuid. (Note that _$.uuid is already very fast - it can generate up to 10 million values in 20 seconds.)
 * @returns {Function} The memoized function.
 */
export let memoize = (fn = req("function")) => {
	let cache = {};
	return function () {
		let args = JSON.stringify(Array.from(arguments));
		let arg_array = Array.from(arguments);
		if (cache[args]) {
			return cache[args];
		} else {
			cache[args] = fn(...arg_array);
			return cache[args];
		}
	};
};
/**
 * Composes two functions together. Read more here: https://www.codementor.io/@michelre/use-function-composition-in-javascript-gkmxos5mj
 * @function
 * @memberOf function
 * @param {...Function} functions The functions to be composed.
 * @returns {Function} The composed function.
 * @example
 * const add2 = (x) => x + 2;
 * const multiply2 = (x) => x * 2;
 * console.log(_$.composeFunction(add2, multiply2)(3)) // 8 - i.e  3 * 2 + 2
 */
export let composeFunction =
	(...functions) =>
	(args) => {
		req("functions", "function list", ![...functions].length);
		return functions.reduceRight((arg, fn) => fn(arg), args);
	};
/**
 * Returns the curried version of a function. Read more here: https://medium.com/@abitoprakash/implementing-a-curry-function-in-javascript-6a249dbcb1bb
 * @function
 * @memberOf function
 * @param {Function} fn The function to curry.
 * @param {Number} [arity=fn.length] The arity (number of params) of the function to curry.
 * {...*} [args] Optional arguments to pass to the function being curried.
 * @returns {Function} The curried version of the function.
 * @example
 * let fn = (x, y, z, w) => x * y * z * w;
 * console.log(_$.curryFunction(fn, 4, 5)(4)(3)(2)); // 120 i.e. 5 * 4 * 3 * 2
 */
export let curryFunction = (
	fn = req("function"),
	arity = fn.length,
	...args
) =>
	arity <= args.length
		? fn(...args)
		: _$.curryFunction.bind(null, fn, arity, ...args);
/**
 * Returns if the given function is async or not.
 * @memberOf function
 * @function
 * @param {Function} val The function to test.
 * @returns {Boolean} True if the function is async and false if not.
 * @example
 * const asyncFn = async (x) => x ** 3; // It's a silly function, but a good example
 * console.log(_$.isAsync(asyncFn)); // true
 */
export let isAsync = (val = req("function")) =>
	Object.prototype.toString.call(val) === "[object AsyncFunction]";

/**
 * Times the function passed.
 * @function
 * @memberOf function
 * @param {Function} fn The function to run and time.
 * @param {String} [name=_$ function timer] The name of the timer
 * @example
 * // Times how long it took the user to enter their name.
 * _$.timeFunction(() => prompt("What's your name?"));
 * @returns {Object} An object with "time" and "function" properties, time being time in milliseconds, and function being the original function passed.
 */
export let timeFunction = (
	fn = req("function"),
	name = "_$ function timer",
) => {
	let startTime = performance.now();
	console.time(name);
	fn();
	console.timeEnd(name);
	return { function: fn, time: performance.now() - startTime };
};
/**
 * Only runs the input function at MAX with the delay specified.
 * @function
 * @memberOf function
 * @param {Function} func The function to run.
 * @param {Object.<Boolean>} options The options.
 * @param {Number} wait The number of milliseconds to wait.
 * @example
 * const alert_function = _$.throttle(() => {alert("hello")}, 5000)
 * setInterval(alert_function, 1)
 * @returns {Function} The throttled function
 */
export let throttle = (
	func = req("function"),
	wait = req("number", "wait"),
	options = {},
) => {
	var context, args, result;
	var timeout = null;
	var previous = 0;
	if (!options) options = {};
	var later = function () {
		previous = options.leading === false ? 0 : Date.now();
		timeout = null;
		result = func.apply(context, args);
		if (!timeout) context = args = null;
	};
	return function () {
		var now = Date.now();
		if (!previous && options.leading === false) previous = now;
		var remaining = wait - (now - previous);
		context = this;
		args = arguments;
		if (remaining <= 0 || remaining > wait) {
			if (timeout) {
				clearTimeout(timeout);
				timeout = null;
			}
			previous = now;
			result = func.apply(context, args);
			if (!timeout) context = args = null;
		} else if (!timeout && options.trailing !== false) {
			timeout = setTimeout(later, remaining);
		}
		return result;
	};
};
/**
 * Debounces a function so that it only runs after it has not been called for a certain amount of time.
 * @memberOf function
 * @function
 * @returns {Function} The debounced function.
 * @example
 * window.addEventListener("keyup", _$.debounce(expensiveFunction, 100));//Run the function expensiveFunction at most every 100ms.
 * @param {Function} func The function to throttle.
 * @param {Number} wait The milliseconds to wait between executions.
 * @param {Boolean} [immediate=false] Whether or not to run immediately, or after a group of executions.
 */
export let debounce = (
	func = req("function"),
	wait = req("number", "wait"),
	immediate = false,
) => {
	// 'private' variable for instance
	// The returned function will be able to reference this due to closure.
	// Each call to the returned function will share this common timer.
	var timeout;

	// Calling debounce returns a new anonymous function
	return function () {
		// reference the context and args for the setTimeout function
		var context = this,
			args = arguments;

		// Should the function be called now? If immediate is true
		//   and not already in a timeout then the answer is: Yes
		var callNow = immediate && !timeout;

		// This is the basic debounce behaviour where you can call this
		//   function several times, but it will only execute once
		//   [before or after imposing a delay].
		//   Each time the returned function is called, the timer starts over.
		clearTimeout(timeout);

		// Set the new timeout
		timeout = setTimeout(function () {
			// Inside the timeout function, clear the timeout variable
			// which will let the next execution run when in 'immediate' mode
			timeout = null;

			// Check if the function already ran with the immediate flag
			if (!immediate) {
				// Call the original function with apply
				// apply lets you define the 'this' object as well as the arguments
				//    (both captured before setTimeout)
				func.apply(context, args);
			}
		}, wait);

		// Immediate mode and no wait timer? Execute the function..
		if (callNow) func.apply(context, args);
	};
};
/**
 * Runs a function asynchronously in a web worker.
 * @function
 * @memberOf function
 * @param {Function} fn The function to run
 * @example
 * _$.runAsync(() =>  "hello world").then(console.log); // "hello world"
 * @returns {Promise} A promise that resolves into the return value of the function.
 */
export let runAsync = (fn = req("function")) => {
	const worker = new Worker(
		URL.createObjectURL(new Blob([`postMessage((${fn})());`]), {
			type: "application/javascript; charset=utf-8",
		}),
	);
	return new Promise((res, rej) => {
		worker.onmessage = ({ data }) => {
			res(data), worker.terminate();
		};
		worker.onerror = (err) => {
			rej(err), worker.terminate();
		};
	});
};
//#endregion Function