Last active
March 14, 2024 17:47
-
-
Save webbower/0de319bd09564d2d8c8c420ef85e007d to your computer and use it in GitHub Desktop.
JS Utilities
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Loop function inspired by Lisp lop macro | |
// @see https://lispcookbook.github.io/cl-cookbook/iteration.html | |
const isStepLoop = config => false; | |
const isIterableLoop = config => false; | |
const isCountedLoop = config => false; | |
const loop = (config, onIter = x => x) => { | |
const { | |
// Loop from `from` to `to` by `step` | |
from, | |
to, | |
step = 1, | |
// Loop through an iterable | |
through, | |
// Loop x number of times | |
times, | |
} = config; | |
if (isStepLoop(config)) { | |
} else if (isIterableLoop)) { | |
} else if (isCountedLoop(config)) { | |
} else { | |
throw new Error('No valid config detected'); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const isValidNumber = x => typeof x === 'number' && !Number.isNaN(x); | |
// Loop function inspired by Lisp loop macro | |
const isRangeLoop = ({ from, to, step }) => [from, to, step].every(isValidNumber); | |
const isIterableLoop = ({ over }) => over != null && typeof over[Symbol.iterator] === 'function'; | |
const isCountedLoop = ({ times }) => isValidNumber(times) && times > 0; | |
const updateCountState = (_, { count, ...rest }) => ({ ...rest, count: count + 1 }); | |
const doRangeLoop = ({ from, to, step, forEach, collect, skipStep, initialState, updateState }) => {}; | |
const doIterableLoop = ({ over, forEach, collect, skipStep, initialState, updateState }) => {}; | |
const doCountedLoop = ({ times, forEach, collect, skipStep, initialState, updateState }) => {}; | |
/** | |
* Loop config object | |
* | |
* @template {Object} [LoopState={ count: number }] The loop state for the loop. Will at least include the iteration count. | |
* @template LoopData=number The loop data. It's a number by default but can be overridden by the type of {@link LoopConfig.over} | |
* @typedef {Object} LoopConfig | |
* @prop {number} from The starting number for a range loop | |
* @prop {number} to The ending number for a range loop | |
* @prop {number} step The step size for a range loop | |
* @prop {Iterable<LoopData>} over An iterable to loop over the contents of | |
* @prop {number} times Define the number of times to do the counted loop | |
* @prop {(loopItem: unknown, loopState: LoopState) => void | unknown} forEach The loop handler | |
* @prop {boolean} collect If `true`, will return the result of forEach handler applied to each list entry in a final array | |
* @prop {(loopItem: unknown, loopState: LoopState) => boolean} skipStep Called on each loop. Will skip the handler when this returns true | |
* @prop {LoopState} initialState The initial loop state. Akin to the first section in a `for` loop | |
* @prop {(loopItem: unknown, loopState: LoopState) => LoopState} updateState Called on each loop. Will skip the handler when this returns true | |
*/ | |
/** | |
* | |
* @param {LoopConfig} config The configuration object for the loop | |
* @returns {void | unknown[]} By default, `loop()` will not return a value. When {@link LoopConfig.collect} is `true`, | |
* `loop()` will return an array of a size equal to the number of times the loop ran (taking | |
* {@link LoopConfig.skipStep} into account) with the result of each loop item passed through the | |
* {@link LoopConfig.forEach} handler | |
*/ | |
const loop = config => { | |
const { | |
// Loop from `from` to `to` by `step` | |
from, | |
to, | |
step = from < to ? 1 : -1, | |
// Loop over an iterable | |
over, | |
// Loop x number of times | |
times, | |
// handlers | |
forEach, | |
// behavior settings | |
/** | |
* If `true`, will return the result of forEach handler applied to each list entry | |
*/ | |
collect = false, | |
/** | |
* Called on each loop. Will skip the handler when this returns true | |
*/ | |
skipStep = () => false, | |
// loop state | |
initialState, | |
updateState = (loopItem, { count }) => ({ count: count + 1 }), | |
} = config; | |
if (isRangeLoop(config)) { | |
} else if (isIterableLoop(config)) { | |
} else if (isCountedLoop(config)) { | |
} else { | |
throw new Error('No valid config detected'); | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const until = (predicate, iterator, initialValue) => { | |
let value = initialValue; | |
while(!predicate(value)) { | |
value = iterator(value); | |
} | |
return value; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Perform logic referencing dynamically fetched data only once each | |
* | |
* In some cases, your code needs to use a value stored in a data structure or is expensive to fetch/generate that requires | |
* verbose code and you may need to reference the value multiple times (e.g. checking if the value is set). Normally, you | |
* would either need to perform the lookup multiple times or store the value in a short-lived variable to only get it once. | |
* | |
* <code> | |
* const value = sessionStorage.getItem('foo') ? sessionStorage.getItem('foo') : 'fallback'; | |
* const tmp = expensiveGetter(); | |
* const value2 = tmp ? tmp : 'fallback' | |
* </code> | |
* | |
* `using()` allows you to shorten the code by only needing reference the generated value(s) once and limits the lifecycle | |
* of temporary variable(s) to only the scope of the operation: | |
* | |
* <code> | |
* const value = using(sessionStorage.getItem('foo'), foo => foo ? foo : 'fallback'); | |
* const value2 = using(expensiveGetter(), val => val ? val : 'fallback';) | |
* </code> | |
* | |
* `using()` takes any number of arguments. The last argument must be a function that will receive all the preceding arguments | |
* in `using()` as its own function arguments. | |
* | |
* <code> | |
* const total = using(1, 2, 3, 4, (a, b, c, d) => a + b + c + d); // 10 | |
*/ | |
const using = (...values) => { | |
if (values.length === 0) { | |
return null; | |
} | |
const fn = values.pop(); | |
if (typeof fn !== 'function') { | |
throw new TypeError('The last argument of `using` must be a function.'); | |
} | |
return fn(...values); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment