function debounce(func, wait_ms, immediate) {
	let timeout;
	const debounced_func = function () {
		// eslint-disable-next-line no-invalid-this
		const context = this;
		const args = arguments;

		const later = () => {
			timeout = null;
			if (!immediate) {
				func.apply(context, args);
			}
		};

		const callNow = immediate && !timeout;

		clearTimeout(timeout);

		timeout = setTimeout(later, wait_ms);

		if (callNow) {
			func.apply(context, args);
		}
	};
	debounced_func.cancel = () => {
		clearTimeout(timeout);
	};
	return debounced_func;
}
@template {any[]} A
@param {(...args: A)=> void} func  function to debounce
@param {number} wait_ms  minimum milliseconds between invocations
@param {boolean=} immediate  trigger the function on the leading edge, instead of the trailing.
@returns {((...args: A)=> void) & {cancel: ()=> void}}  a function, that, as long as it continues to be invoked, will not be triggered.
  The function will be called after it stops being called for `wait_ms` milliseconds.

@example
window.addEventListener("resize", debounce(() => {
 console.log(window.innerWidth);
}, 250));