Functional programming with async and type safety
npm install gamlagamla is a zero-deps functional programming library for JavaScript/TypeScript,
denoo and nodejs.
nodejs: npm install gamla
deno: import { pipe } from "https://deno.land/x/gamla/src/index.ts";
This library allows you to write in typescript/javascript using composition.
It has three main advantages over the native functional APIs and similar libs
(ramda and lodash):
1. It helps you combine async functions with regular ones, without having to
change your code.
1. As opposed to ramda and lodash, It keeps typing information, so you get
type safety when programming in pipelines.
1. As opposed to ramda and lodash, you get a stack trace that logs your
compositions too, so you can debug as usual.
``ts
type Person = { name: string; age: number };
const people: Person[] = [
{ name: "alice", age: 28 },
{ name: "bob", age: 22 },
{ name: "carroll", age: 76 },
];
const getNamesOfPeopleOlderThan25 = pipe(
filter(({ age }) => age > 25),
sideEffect(console.log), // Log mid pipeline.
map(({ name }) => name), // Get people names only.
join(", "),
);
console.log(getNamesOfPeopleOlderThan25(people)); // "alice, carroll"
`
Now let's imagine you wanted to call a remote server for some information
somewhere in this pipeline. Usually this means refactoring your entire program
to async functions.
But pipe is smart and allows you to change one function without the ones
around it. No collateral refactoring is needed.
`ts
// Call some remote server to get hobbies for a person.
const getHobbies = async (person: Person): string[] => {...}
const isAnyoneUnder25InterestedInGolfing = pipe(
filter(({ age }: Person) => age < 25),
// Async function mid pipeline, even tho functions before and after are not.
// Also flatten the result.
mapCat(getHobbies),
includes('golfing'),
);
console.log(await isAnyoneUnder25InterestedInGolfing(people)); // probably false :)`
gamla also has a bunch of methods to facilitate parallel IO operations.
Consider the following case:
- you have a list of 1000 items
- you have an async function process(item)
- you need to process all items
- it needs to be done concurrently, but not more than 25 at a time
This seemingly complex list of requirements is a simple readable one liner:
`ts`
map(throttle(25, process))(items);
Here's another example, the OpenAI API has rate limitations, which will block
your requests at some point. rateLimit can help you avoid these exceptions.
`ts
// This is how each request's weight is computed.
const weightFn = pipe(
map(({ content }: ChatCompletionMessage) => (content || "").length),
sum,
divide(4), // For english, a token is around 4 characters.
);
const callAPI = (messages: ChatCompletionMessage[]) =>
new OpenAI(assertString(OpenAIToken)).createChatCompletion({
model: "gpt-4",
messages,
});
// 10KTPM-200RPM is the official limitation for GPT-4, this means maximum 200 requests per minute, and not more than 10000 tokens.
const callAPIWithRateLimiter = rateLimit(
200, // Max requests in time window.
10000, // Max total 'weight' in time window.
60 * 1000, // Time window to apply the limitation.
weightFn,
callAPI,
);
`
gamla preserves typing, so if you by accident you write something like this:
`tsfilter
const typingMismatch = pipe(
filter(({ age }: Person) => age < 25),
(x: number) => x + 1, // The result of here is an array of Person, not a number!`
);
You will get a typing error.
gamla has a lot of utils for debugging. The most useful ones are sideLog,sideLogAfter and sideLogBefore. Their prpose is to allow logging without
moving any code around.
E.g. you have a complex expression like so:
`ts`
someCondition ? f(a + g(b)) : c;
And you want to log the value of g(b) without inteferring with the code.console.log
Usually that would require rewriting a bit, placing the value into a variable
and using . But with sideLog you can just do:
`ts`
someCondition ? f(a + sideLog(g(b))) : c;
Similarly, if you're working with pipelines and want to log somewhere in the
middle:
`tsg
pipe(
f,
g,
sideLog, // Would log the output of .`
h,
);
If you want to keep typing information, use sideLogAfter or sideLogBefore:
`tsg
pipe(f, sideLogAfter(g), h); // Would log the output of .
pipe(f, g, sideLogBefore(h)); // Would log the input to h. So same.`
All functions
`typescript`
anymap
The anymap function takes a predicate function f and returns a new functionxs
that takes an array and checks if any element in the array satisfies thef
predicate . It returns a boolean value indicating the result.
#### Example
`typescript
const numbers = [1, 2, 3, 4, 5];
const isEven = (x: number) => x % 2 === 0;
const anyEven = anymap(isEven);
const result = anyEven(numbers); // checks if any number in the array is even
console.log(result); // Output: true
`
In the example above, the anymap function is used to create a new functionanyEven that checks if any number in an array is even. The result is true[1, 2, 3, 4, 5]
because there is at least one even number in the array .
(f: (x: X) => boolean) => (xs: X[]) => xs.every(f)
This function takes a predicate function f and returns a new function thatxs
takes an array and returns true if f returns true for every elementxs
in , or false otherwise.
#### Parameters:
- f: (x: X) => boolean - A predicate function that takes an element x ofX
type and returns a boolean value. It determines the condition to be
checked for each element in the input array.
#### Returns:
- (xs: X[]) => boolean - A function that takes an array xs of type X andtrue
returns if f returns true for every element in xs, or false
otherwise.
#### Example
`typescript
const isEven = (x: number) => x % 2 === 0;
const allNumbersEven = allmap(isEven);
const result = allNumbersEven([2, 4, 6, 8]);
console.log(result); // Output: true
`
In the above example, the allNumbersEven function is created by passing theisEven function to allmap. It checks if every element in the input array[2, 4, 6, 8] is even using the isEven function. The resulting value istrue, as all numbers in the array are even.
Signature: (str: string) => (x: (string | number)[]) => string
This function takes a string parameter str as its first argument and returnsx
a new function. The returned function takes an array of string or numberstr
elements and joins them into a single string using the specified separator.
Example
`javascript`
const numbers = [1, 2, 3, 4, 5];
const joinWithComma = join(",");
console.log(joinWithComma(numbers));
Output:
``
"1,2,3,4,5"
The function is curried, allowing you to partially apply the string separator
before applying it to the array.
Returns the number of elements in an array.
#### Signature
`typescript`
length
#### Parameters
- array: An array of type T[]. The array for which the length is to be
determined.
#### Returns
The function returns a number representing the number of elements in the array.
#### Example
`typescript`
const array = [1, 2, 3, 4, 5];
const result = length(array);
console.log(result); // Output: 5
`typescript`
unique
This function takes an array and a key function as parameters and returns a new
array with unique items based on the key.
- key: A function that extracts a primitive value from each item in the array.
Example
`typescript
const array = [
{ id: 1, name: "John" },
{ id: 2, name: "Jane" },
{ id: 1, name: "John" },
{ id: 3, name: "John" },
];
const uniqueById = unique((item) => item.id);
const uniqueArray = uniqueById(array);
console.log(uniqueArray);
// Output: [
// { id: 1, name: 'John' },
// { id: 2, name: 'Jane' },
// { id: 3, name: 'John' },
// ]
`
In this example, the unique function is used to remove duplicates from theid
array of objects based on the property. The resulting array only containsid
objects with unique values.
Concatenates an array of arrays into a single array.
Signature
`typescript`
(array: unknown[][]) => any[]
Parameters
- array: An array of arrays to be concatenated. Each sub-array can contain
elements of any type.
Returns
- Returns a new array containing all elements from the input arrays.
Example
`typescript`
const result = concat([[1, 2], [3, 4], [5, 6]]);
console.log(result); // Output: [1, 2, 3, 4, 5, 6]
In the example above, the concat function is called with an input array[[1, 2], [3, 4], [5, 6]]. It returns a new array [1, 2, 3, 4, 5, 6] where
the elements from the sub-arrays are concatenated into a single array.
Signature: reverse(array: Input): Reversed
Parameters:
- array - An array of unknown type.
Return Type: Reversed
The reverse function takes an array and returns a new array with the elements
reversed.
Example
`typescript`
const arr = [1, 2, 3, 4];
const reversedArray = reverse(arr);
console.log(reversedArray); // Output: [4, 3, 2, 1]
`typescript`
tail(x: unknown[]): unknown[]
The tail function takes an array x and returns a new array with all the
elements except the first element.
#### Example
`typescript`
const arr = [1, 2, 3, 4, 5];
const result = tail(arr);
console.log(result);
// Output: [2, 3, 4, 5]
In the example above, the tail function is used to remove the first element ofarr
the array. The resulting array [2, 3, 4, 5] is then stored in theresult variable and logged to the console.
Returns the first element of an array or string.
#### Signature
`typescript`
head
#### Parameters
- x: The array or string from which to retrieve the first element.
#### Returns
The first element of the given array or string.
#### Example
`typescript
const arr = [1, 2, 3, 4];
const str = "Hello";
const firstElementOfArr = head(arr); // 1
const firstCharacterOfStr = head(str); // "H"
`
init(x: unknown[]): unknown[]
This function takes an array x and returns a new array with all elements
except the last one.
#### Parameters
- x: unknown[]: The array to be modified.
#### Returns
- unknown[]: A new array with all elements of x except the last one.
#### Example
`javascript`
const arr = [1, 2, 3, 4, 5];
const result = init(arr);
console.log(result); // Output: [1, 2, 3, 4]
In the above example, the init function is called with the arr array as an[1, 2, 3, 4]
argument. The function returns a new array , which contains allarr
elements of the original array except the last one.
`typescript`
function second
The second function takes an argument x of type T, where T is an array1
or a string. It returns the second element (index ) of the array or string.
#### Example
`typescript`
console.log(second([1, 2, 3])); // Output: 2
console.log(second("hello")); // Output: 'e'
In the first example, the second function returns 2, which is the second[1, 2, 3]
element in the array . In the second example, it returns 'e', which'hello'
is the second character in the string .
Signature: function third
Returns the third element of the input array or string.
- T generic type that extends an array or string.x
- the input array or string.
Example
`typescript`
third([1, 2, 3, 4]); // returns 3
third("hello"); // returns 'l'
Signature:
The last function takes an array x of type T[] and returns the last
element of the array.
Example
`typescript`
const numbers = [1, 2, 3, 4, 5];
const lastNumber = last(numbers);
console.log(lastNumber); // Output: 5
`typescript`
const names = ["Alice", "Bob", "Charlie", "David"];
const lastName = last(names);
console.log(lastName); // Output: "David"
`typescript`
empty
This function checks if an array is empty.
#### Parameters
- x: T[] - The array to check if it is empty.
#### Returns
- boolean - Returns true if the array is empty, false otherwise.
#### Example
`typescript
const arr1 = [1, 2, 3];
const arr2 = [];
empty(arr1); // false
empty(arr2); // true
`
This function checks if an array x is non-empty.
#### Parameters
- x: T[] - an array of any type T
#### Returns
- boolean - true if the array x is non-empty, false otherwise
#### Example
`typescript
const arr1: number[] = [1, 2, 3];
const arr2: string[] = [];
const arr3: boolean[] = [true, false];
console.log(nonempty(arr1)); // Output: true
console.log(nonempty(arr2)); // Output: false
console.log(nonempty(arr3)); // Output: true
`
Signature: (x: T) => T[]
#### Description:
This function takes an input value x and returns an array containing x as
its only element. It essentially wraps the given value in an array.
#### Example
`typescript`
wrapArray("hello"); // returns ["hello"]
wrapArray(42); // returns [42]
wrapArray({ name: "John", age: 25 }); // returns [{ name: "John", age: 25 }]
`typescript`
zip
...args: T
): { [K in keyof T]: T[K] extends (infer V)[] ? V : never }[]
The zip function takes in multiple arrays as arguments (spread syntax) and
returns a new array composed of the corresponding elements from each input
array.
#### Parameters
- ...args: T: The arrays to zip together. The type T represents a tuple of
arrays, where each array may have different element types.
#### Return Type
The return type is an array of tuples, where each tuple contains the
corresponding elements from the input arrays. The element types of the tuples
are inferred from the input arrays, and any arrays with different element types
will result in a type of never for that position in the resulting tuple.
#### Example
`typescript
const array1 = [1, 2, 3];
const array2 = ["a", "b", "c"];
const array3 = [true, false, true];
const zipped = zip(array1, array2, array3);
// zipped = [
// [1, 'a', true],
// [2, 'b', false],
// [3, 'c', true],
// ]
`
In this example, the zip function is called with three arrays of differentzipped
element types. The resulting array contains tuples where the first
element is a number, the second element is a string, and the third element is a
boolean.
`typescript`
comparator:
((x: X, y: X) => number | boolean);
The sortCompare function is a higher-order function that takes a comparator
function as input and returns a new function that can be used to sort an array.
#### Example
`typescript`
const numbers = [3, 1, 2];
const descendingComparator = (x: number, y: number) => y - x;
const sortedNumbers = sortCompare(descendingComparator)(numbers);
console.log(sortedNumbers); // [3, 2, 1]
In this example, the sortCompare function is used to sort an array of numberscomparator
in descending order using a custom function. The resulting sorted
array is then logged to the console.
sortKey is a higher-order function that takes a key function as a parametersortCompare
and returns a function.
#### Signature
`typescript`
sortKey
#### Parameters
- key : A function that takes an element of type X and returns a value ofComparable
type . This value will be used to compare elements during sorting.
#### Return Type
- (xs: X[]) => X[] : A function that takes an array of type X and returns aX
sorted array of type based on the key function.
#### Example
`typescript`
const sortByAge = sortKey((person) => person.age);
const people = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 },
{ name: "Charlie", age: 20 },
];
const sortedPeople = sortByAge(people);
console.log(sortedPeople);
// Output: [
// { name: "Charlie", age: 20 },
// { name: "Alice", age: 25 },
// { name: "Bob", age: 30 }
// ]
In the example above, sortKey is used to create a sortByAge function thatkey
can sort an array of people based on their age. The function is provided(person) => person.age
as a lambda function . The sortByAge function is thenpeople
used to sort the array and the result is stored in the sortedPeoplesortedPeople
array. The array is then printed to the console showing the
sorted order based on the age of the people.
`typescript`
range(start: number, end: number): any[]
Creates an array of numbers from start to end (exclusive).
#### Parameters
- start: The starting number of the range.end
- : The ending number of the range (exclusive).
#### Return Type
- any[]: An array of numbers.
#### Example
`typescript`
const numbers = range(1, 5);
console.log(numbers);
// Output: [1, 2, 3, 4]
In this example, range(1, 5) returns an array of numbers from 1 to 4[1, 2, 3, 4]
(exclusive). The resulting array is .
Signature: (x: T) => (array: T[]) => boolean
The contains function takes a value x of generic type T and returns aarray
closure that takes an array of type T[] and returns a boolean value
indicating whether the array contains the given value or not.
Example
`javascript`
const checkForValue = contains(3);
console.log(checkForValue([1, 2, 3, 4])); // true
console.log(checkForValue([5, 6, 7])); // false
Returns a function that checks if a given value is included in an array.
#### Signature
`typescript`
(
#### Parameters
- array: An array of values to check if the given value is included.
#### Returns
A function that takes a value (x) and returns true if the value is includedfalse
in the array, otherwise .
#### Example
`javascript
const fruits = ["apple", "banana", "orange"];
const isIncluded = includedIn(fruits);
console.log(isIncluded("apple")); // true
console.log(isIncluded("grape")); // false
`
Signature: (n: number) => (xs: T[]) => T[]
This function takes a number n and returns a function that takes an array xsn
and returns a new array containing the first elements of xs.
#### Example
`typescript`
const takeThree = take(3);
const numbers = [1, 2, 3, 4, 5];
console.log(takeThree(numbers)); // Output: [1, 2, 3]
(n: number) => (xs: T[]) => T[]
This function takes a number n as its argument and returns another functionxs
that accepts an array . It returns a new array containing the elements ofxs starting from index n onwards.
#### Example
`typescript`
const dropThree = drop(3);
const numbers = [1, 2, 3, 4, 5];
const result = dropThree(numbers); // [4, 5]
`typescript`
enumerate
The enumerate function takes an array xs and returns an array of arrays of
pairs containing the index and value of each element in the original array.
#### Parameters
- xs: T[] : The input array.
#### Returns
(number | T)[][] : An array of arrays where each inner array contains the
index and value of an element from the input array.
#### Example
`typescript`
const array = ["a", "b", "c"];
const result = enumerate(array);
// result is [[0, 'a'], [1, 'b'], [2, 'c']]
`typescript`
(
xs.flatMap((_, i) => (i <= xs.length - l ? [xs.slice(i, i + l)] : [])));
Creates a function that returns a sliding window view of a given array.
#### Parameters
- l: number: The size of the sliding window.
#### Returns
- (xs: T[]) => any: A function that takes an array of type T and returns an
array of sliding window views.
#### Example
`typescript`
const getWindowViews = slidingWindow(3);
const inputArray = [1, 2, 3, 4, 5];
const outputArray = getWindowViews(inputArray);
console.log(outputArray); // [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
In the example above, the slidingWindow function is used to create a functiongetWindowViews that generates sliding window views of size 3. The inputArraygetWindowViews
is then passed to and the resulting outputArray contains all[[1, 2, 3], [2, 3, 4], [3, 4, 5]]
sliding window views: .
Creates a pipeline of functions by composing them together. The output of one
function serves as the input to the next function in the pipeline.
#### Signature
`typescript`
pipe
#### Parameters
- ...fs: ValidPipe: Rest parameter that accepts a series of functions to
be piped together. Each function in the pipeline should have compatible input
and output types.
#### Return Type
- Pipeline: The type of the pipeline, which represents a function that
takes multiple arguments and returns the final output after applying each
function in the pipeline.
#### Example
`typescript
const add = (a: number, b: number): number => a + b;
const double = (x: number): number => x * 2;
const subtract = (a: number, b: number): number => a - b;
const myPipeline = pipe(add, double, subtract);
const result = myPipeline(5, 2); // Result: ((5 + 2) * 2) - 2 = 12
`
In the example above, myPipeline is created by piping together the add,double, and subtract functions. When myPipeline is called with arguments5 and 2, it applies each function in the pipeline sequentially and returns12
the final output .
Signature:
`typescript`
compose
Compose takes in an array of functions and returns a new function that is the
composition of these functions. The returned function takes in an initial input
and passes it through each function in the array, applying them in reverse
order.
If the array of functions is a valid pipe (i.e., each function's return type
matches the argument type of the next function), the return type of the composed
function is a pipeline of the reversed array of functions. Otherwise, it returns
never.
Example:
`typescript
const addOne = (num: number) => num + 1;
const double = (num: number) => num * 2;
const subtract = (num1: number, num2: number) => num1 - num2;
const composed = compose(addOne, double, subtract);
const result = composed(5, 2);
console.log(result); // Output: 16
`
In the example above, we have three functions: addOne, double, andsubtract. We use compose to create a new function composed by composingcomposed
these functions. When we invoke with the arguments 5 and 2, thesubtract
composition is applied in reverse order: is first called with the5
arguments and 2, the result is then passed to double, and finally thedouble
output of is passed to addOne. The final result is 16.
`typescript`
(
pipe(g, f));
The after function is a higher-order function that takes another function fg
as an argument and returns a new function. The returned function takes a generic
function as an argument and returns the result of piping g through fpipe
using the function.
#### Example
`typescript
const double = (value: number): number => value * 2;
const square = (value: number): number => value * value;
const calculate = after(square)(double);
console.log(calculate(3)); // Output: 18
`
In the above example, after(square)(double) returns a new functioncalculate. When calculate is called with the argument 3, it pipes thesquare
argument through and then through double, resulting in the output18 (3 3 2 = 18).
`typescript`
before
The before function takes in a function f1 and returns a higher-orderf2
function that takes in another function . It then returns a Pipeline thatf1
consists of and f2, with f1 as the first function in the pipeline andf2 as the second function.
#### Example
`typescript
const addOne = (num: number) => num + 1;
const double = (num: number) => num * 2;
const pipeline = before(addOne)(double);
const result = pipeline(5); // 12
`
In the above example, addOne is the first function in the pipeline, anddouble is the second function. When the pipeline is called with the input5, it applies addOne first, resulting in 6, and then applies double,12
resulting in .
Signature: complement(f: F): (...x: Parameters
The complement function takes a function f as input and returns a newf
function that is the logical complement of . The returned function takes thef
same arguments as and returns a boolean value based on the negation of thef
result of .
Parameters:
- f: The function to be complemented.
Return Type: (...x: Parameters
Example
`typescript
const greaterThanTen = (num: number) => num > 10;
const isLessThanOrEqualToTen = complement(greaterThanTen);
console.log(isLessThanOrEqualToTen(5)); // true
console.log(isLessThanOrEqualToTen(15)); // false
`
In the above example, the complement function is used to create a new functionisLessThanOrEqualToTen that is the logical complement of the greaterThanTenisLessThanOrEqualToTen
function. When is called with a number, it returnstrue if the number is less than or equal to 10, and false otherwise.
`typescript`
(
f(x);
return x;
});
The sideEffect function is a higher-order function that takes a function ff
as its parameter. The parameter is a function that accepts a value of typeT and has a return type of void. The sideEffect function returns anotherT
function that also takes a value of type as its parameter and returns the
same value.
The purpose of the sideEffect function is to enable side effects by executingf
the function with a given value of type T, while still returning that
value. This allows for the execution of side effects without losing the
reference to the value being operated on.
#### Example
`javascript
const printAndReturn = sideEffect(console.log);
const result = printAndReturn("Hello, World!");
// Output: Hello, World!
console.log(result); // 'Hello, World!'
`
In this example, the sideEffect function is used to wrap the console.logprintAndReturn
function, enabling it to print a message to the console and return the same
message. The function is then used to executeconsole.log('Hello, World!'), and the result is stored in the result
variable, which is then logged to the console.
Signature:
`typescript`
(
cleanup: (...args: Args) => void | Promise
) =>
(f: (...args: Args) => Result) =>
(...args: Args) => any);
Parameters:
- cleanup: A function that takes in any number of arguments Args and returnsvoid
either or Promise. This function will be executed after thef
wrapped function is called.
Return Type:
(f: (...args: Args) => Result) => (...args: Args) => any
Description:
The wrapSideEffect function takes in a cleanup function and returns a newf
function that wraps another function . The returned function takes in anyArgs
number of arguments and returns a function that will execute the cleanupf
function after executing the wrapped function .
If the result of f is a Promise, the cleanup function will be executed afterPromise
the promise resolves. If the cleanup function also returns a , the
final result will be the result of the wrapped function. Otherwise, the final
result will be the result of the cleanup function.
Example
`typescriptCleaning up with arguments ${arg1} and ${arg2}
const cleanupFunction = (arg1: string, arg2: number) => {
console.log();
};
const wrappedFunction = wrapSideEffect(cleanupFunction)(
(arg1: string, arg2: number) => {
console.log(
Executing wrapped function with arguments ${arg1} and ${arg2},
);
return arg1 + arg2;
},
);
wrappedFunction("Hello", 123);
// Output:
// Executing wrapped function with arguments Hello and 123
// Cleaning up with arguments Hello and 123
// Result: Hello123
`
`typescript`
applyTo(...args: A): (f: (...args: A) => unknown) => unknown
This higher-order function takes in a variable number of arguments args ofA
type , and returns another function that takes in a function f whichargs
accepts the same arguments and returns a value of type unknown.
#### Parameters
- ...args: A: A variadic parameter representing a variable number of argumentsA
of type .
#### Return Type
(f: (...args: A) => unknown) => unknown: A function that accepts a functionf and returns a value of type unknown.
#### Example
`typescript`
const addNumbers = (a: number, b: number) => a + b;
const applyToExample = applyTo(10, 20);
const result = applyToExample(addNumbers); // 30
Signature: always
Creates a function that always returns the same value.
- x: The value to be always returned.
Returns: A function that when called, always returns the provided value x.
Example
`typescript`
const constantFunc = always(42);
console.log(constantFunc()); // Output: 42
Signature: (x: T) => T
The identity function takes in an argument x of type T and returns it as
is, without any modifications.
Example
`typescript
const result: number = identity(5);
console.log(result); // Output: 5
const result2: string = identity("hello");
console.log(result2); // Output: "hello"
`
`typescript`
ifElse
predicate: Predicate,
fTrue: If,
fFalse: Else,
): (...x: Parameters
This function takes a predicate, a function to execute if the predicate is true,
and a function to execute if the predicate is false. It returns a new function
that accepts the same arguments as the predicate and invokes either fTrue orfFalse based on the result of the predicate.
#### Example
`typescript
const isEven = (num: number): boolean => num % 2 === 0;
const multiplyByTwo = (num: number): number => num * 2;
const divideByTwo = (num: number): number => num / 2;
const conditionalFunction = ifElse(
isEven,
multiplyByTwo,
divideByTwo,
);
console.log(conditionalFunction(4)); // Output: 8
console.log(conditionalFunction(5)); // Output: 2.5
`
In the example above, the ifElse function is used to create a new functionconditionalFunction
called . This function checks if a given number is even,conditionalFunction
and if it is, it multiplies it by two. If the number is odd, it divides it by
two. The is then invoked with different input numbers to
test the conditional logic.
`typescript`
unless
| ((_: any) => Promise
)>(
predicate: Predicate,
fFalse: (_: Parameters
): (...x: Parameters
The unless function takes a predicate function and a fFalse function. Itpredicate
returns a function that takes any number of arguments and checks if the is false. If the predicate is false, it calls the fFalsepredicate
function with the arguments and returns the result. If the is true,
it returns the arguments.
Example
`typescript
const isFalsey = (x: any) => !x;
const fFalse = (x: any) => The value ${x} is false;
const result = unless(isFalsey, fFalse)(false);
console.log(result); // The value false is false
`
In this example, the unless function is used to check if the value false isfFalse
falsey. Since it is falsey, the function is called with the valuefalse and the result is returned.
`typescript`
when
| ((_: any) => Promise
)>(predicate: Predicate, fTrue: (_: Parameters
The when function is a higher-order function that takes a predicate function
and a callback function. It returns a new function that checks if the predicate
function returns true, and if so, calls the callback function.
#### Parameters
- predicate: Predicate - The predicate function that determines whether thefTrue: (_: Parameters
callback function should be called.
- - The callback function to be
called if the predicate function returns true.
#### Return Value
The return value of when depends on the types of the Predicate and fTruePredicate
parameters. If the is an AsyncFunction, the return value is aPromise. Otherwise, it is any.
#### Example
`typescript
const isEven = (num: number) => num % 2 === 0;
const callback = (num: number) => {
console.log(${num} is even);
};
const whenIsEven = when(isEven, callback);
whenIsEven(10); // logs "10 is even"
whenIsEven(5); // does nothing
`
In this example, the isEven function is used as the predicate to check if acallback
number is even. If the number is even, the function is called andwhenIsEven
logs a message. The function is created using the when function,
and when called with an even number, it logs a message.
`typescript
CondElements extends CondElement
cond(
predicatesAndResolvers: CondElements,
): (
...x: Parameters
) => any
`
The cond function takes an array of predicates and resolvers and returns a
function that when called with arguments, runs each predicate on the arguments
and returns the result of the first resolver that matches the predicate.
#### Example
`typescript
const isEven = (x: number) => x % 2 === 0;
const double = (x: number) => x * 2;
const triple = (x: number) => x * 3;
const resolver = cond([
[isEven, double],
[() => true, triple],
]);
console.log(resolver(4)); // Output: 8
console.log(resolver(3)); // Output: 9
`
Signature: (x: any[]) => (y: T) => T
#### Description:
This function takes in any number of arguments, ...x, and returns a newy
function that takes in a value . The returned function logs the argumentsx
passed in as , along with y, to the console, and then returns y.
#### Example
`typescript`
const log = logWith("Hello");
const result = log("World");
// Output: Hello World
// result: "World"
`typescript`
asyncTimeit
handler: (time: number, args: Args, result: R) => void,
f: (..._: Args) => R,
): (...args: Args) => Promise
The asyncTimeit function is a higher-order function that takes a handlerf
function and another function , and returns a new function that measures thef
execution time of and invokes the handler with the elapsed time, arguments,f
and result of . The returned function is asynchronous and returns a promise
of the result.
#### Parameters
- handler: (time: number, args: Args, result: R) => void - The handlerf
function to be invoked with the elapsed time, arguments, and result of .f: (..._: Args) => R
- - The function to be measured.
#### Returns
A new function that is asynchronous and returns a promise of the result of f.
#### Example
`typescript
const fetchData = async (url: string) => {
const response = await fetch(url);
const data = await response.json();
return data;
};
const timeHandler = (time: number, args: [string], result: any) => {
console.log(Fetch from ${args[0]} took ${time} milliseconds);
};
const timedFetchData = asyncTimeit(timeHandler, fetchData);
const data = await timedFetchData("https://example.com/api/data");
console.log(data);
`
In the example above, the timedFetchData function is created by passing thetimeHandler and fetchData functions to asyncTimeit. When timedFetchDatafetchData
is called with a URL, it measures the execution time of and logs thefetchData
elapsed time. The result of is returned and can be used further in
the code.
Signature:
(handler: (time: number, args: Args, result: R) => void, f: (..._: Args) => R) => (...args: Args) => R
This function is a higher-order function that takes two parameters:
- handler: A function that receives three parameters: time (duration inargs
milliseconds), (the arguments passed to the inner function), andresult
(the result returned by the inner function).f
- : The inner function that will be timed.
The timeit function returns a new function that has the same signature as the(...args: Args) => R
inner function (). This new function will measure thehandler
execution time of the inner function and call the function with the
measured time, arguments, and result.
Example
`typescriptExecution time: ${time}ms
const logTime = (time: number, args: number[], result: number) => {
console.log();Arguments: ${args}
console.log();Result: ${result}
console.log();
};
const add = (a: number, b: number) => a + b;
const timedAdd = timeit(logTime, add);
timedAdd(2, 3);
// Output:
// Execution time:
// Arguments: 2, 3
// Result: 5
`
In this example, the logTime function logs the execution time, arguments, andadd
result. The function adds two numbers. The timedAdd function is createdtimeit
using , passing logTime as the handler and add as the innertimedAdd
function. When is called with arguments 2 and 3, it measures theadd
execution time of and logs the time, arguments, and result.
`typescript`
(
The assert function takes a condition and an error message as parameters andT
returns a function that takes a value of type and returns that value if the
condition is true. If the condition is false, it throws an error with the given
error message.
Example usage:
`typescript`
const greaterThanZero = assert(
(x: number) => x > 0,
"Number must be greater than zero",
);
const result = greaterThanZero(5); // returns 5
const error = greaterThanZero(-2); // throws an error with the message "Number must be greater than zero"
`typescript`
filter
_: ParamOf
) => true extends IsAsync
This function takes in a predicate function f and returns a new function that
filters an array based on that predicate. The filtered array is returned as the
result.
- f: The predicate function that determines whether an element should be
included in the filtered array or not.
Example
`typescript
const isEven = (num: number) => num % 2 === 0;
const numbers = [1, 2, 3, 4, 5];
const filteredNumbers = filter(isEven)(numbers);
console.log(filteredNumbers); // Output: [2, 4]
`
In the above example, the filter function is used to filter out the evennumbers
numbers from the array. The resulting filtered array contains only the
even numbers [2, 4].
#### Signature
`typescript`
((Fn: Predicate) => Pipeline);
#### Description
The find function takes a predicate function as an argument and returns a
pipeline that filters an array based on the given predicate and returns the
first element of the filtered array.
#### Example
`typescript
const animals = ["cat", "dog", "elephant", "bird"];
const startsWithC = (animal) => animal.startsWith("c");
const result = find(startsWithC)(animals);
console.log(result); // 'cat'
`
In the above example, the find function is used to filter the animals arraystartsWithC
based on the predicate function and returns the first element that'cat'
matches the predicate, which is .
#### Return Type
`typescript`
Pipeline<
[
(
_: ParamOf
) => Fn extends AsyncFunction ? Promise
]
>;
The find function returns a pipeline that takes an array as input and returns
the first element of the filtered array based on the provided predicate
function.
`typescript`
remove
The remove function takes a predicate f and returns a new function that
removes elements from an array that satisfy the predicate.
#### Parameters
- f: F: The predicate function to test each element of the array. The functionf
should accept an argument of the same type as the elements in the array
and return a boolean.
#### Returns
- (x: Parameters: A new function that accepts
an array and returns a new array with elements removed based on the provided
predicate.
#### Example
`typescript
const numbers = [1, 2, 3, 4, 5];
const isEven = (n: number): boolean => n % 2 === 0;
const removeEven = remove(isEven);
const result = removeEven(numbers);
console.log(result);
// Output: [1, 3, 5]
`
In the example above, the remove function is used to create a new functionremoveEven that removes even numbers from an array. The result array[1, 3, 5]
contains only the odd numbers .
`typescript`
The intersectBy function takes a function f that maps elements of type TPrimitive
to , and returns another function that takes an array of arrays ofT
type (arrays). It then intersects all the arrays in arrays based on thef
values of , and returns an array containing the common elements.
Parameters:
- f: (x: T) => Primitive - A function that maps elements of type T toPrimitive
. This function will be used to determine the intersections.
Returns:
- (arrays: T[][]) => T[] - The function takes an array of arrays of type TT
and returns an array of type containing the common elements.
Example
`typescript
const numbers = [[1, 2, 3, 4, 5], [2, 4, 6, 8, 10], [3, 6, 9, 12, 15]];
const intersectByPrimitive = intersectBy((x) => x % 10);
console.log(intersectByPrimitive(numbers));
// Output: [2, 4, 6]
const strings = [
["apple", "banana", "cherry"],
["banana", "kiwi", "pineapple"],
["cherry", "kiwi", "orange"],
];
const intersectByLength = intersectBy((x) => x.length);
console.log(intersectByLength(strings));
// Output: ['banana', 'kiwi']
`
In the example above, we have two arrays numbers and strings. TheintersectByPrimitive function is created by passing a function that mapsnumbers
numbers to their remainder when divided by 10. The resulting function is then
used to intersect the arrays in , resulting in an array [2, 4, 6],numbers
which are the elements common to all arrays in .
Similarly, the intersectByLength function is created by passing a functionstrings
that maps strings to their lengths. The resulting function is then used to
intersect the arrays in , resulting in an array ['banana', 'kiwi'],strings
which are the strings common to all arrays in based on their lengths.
`typescript`
batch(
keyFn: (_: TaskInput) => TaskKey,
maxWaitMilliseconds: number,
execute: Executor
condition: (_: TaskInput[]) => boolean,
): Pipeline<[(x: TaskInput) => JuxtOutput<[(x: TaskInput) => TaskInput, (_: TaskInput) => TaskKey]>, ([input, key]: [TaskInput, TaskKey]) => Promise
The batch function takes in four parameters: keyFn, maxWaitMilliseconds,execute, and condition. It returns a pipeline that consists of two steps.
- keyFn is a function used to determine the key for each task. It takes in aTaskInput
and returns a TaskKey.maxWaitMilliseconds
- is the maximum time to wait before executing the batchedexecute
tasks.
- is the function responsible for executing the tasks. It takes in aTaskInput
and returns an Output.condition
- is a function that determines whether the batched tasks should beTaskInput
executed or not. It takes in an array of and returns a boolean.
The first step of the pipeline is a juxt, which combines two functions:
- The first function in the juxt is (x: TaskInput) => TaskInput, which simplyTaskInput
returns the .(_: TaskInput) => TaskKey
- The second function in the juxt is , which uses thekeyFn
to determine the key for the task.
The second step of the pipeline is a function that takes in [input, key],ElementOf
which is the output of the previous step. It returns a promise that resolves to
the
Example
`typescript
const keyFn = (input: string) => input.charAt(0);
const maxWaitMilliseconds = 1000;
const execute = (input: string) => input.toUpperCase();
const condition = (inputs: string[]) => inputs.length >= 3;
const batchedTask = batch(keyFn, maxWaitMilliseconds, execute, condition);
const promise = batchedTask("apple");
promise.then((result) => {
console.log(result); // Output: "APPLE"
});
`
In this example, the batchedTask function is created with the providedkeyFn, maxWaitMilliseconds, execute, and condition. When thebatchedTask is called with the input "apple", it will wait for more tasks withexecute
the same key (in this case, the first character of the input) to be batched or
wait for the maximum wait time before executing the batched tasks. Once
executed, the promise will resolve with the output of the function,
which in this case is "APPLE".
timeout is a function that takes in a timeout duration in milliseconds, a
fallback function, and an asynchronous function. It returns a new function that
incorporates the timeout functionality.
#### Signature
`typescript`
timeout
ms: number,
fallback: (..._: Args) => Output | Promise
#### Parameters
- ms: number: The duration for the timeout in milliseconds.fallback: (..._: Args) => Output | Promise
- : The fallback function tof: (..._: Args) => Promise
be called if the async function does not resolve within the timeout duration.
- : The asynchronous function to be
executed.
#### Return Type
(...args: Args) => Promise: The returned function takes the same
arguments as the original asynchronous function and returns a promise that
resolves to the output of the original function or the fallback function.
#### Example
`typescript
const doSomethingAsync = async (arg: string) => {
// ... some time-consuming asynchronous operation
return arg.toUpperCase();
};
const timeoutDoSomething = timeout(2000, () => "Timeout", doSomethingAsync);
timeoutDoSomething("hello")
.then(console.log)
.catch(console.error);
`
In this example, the timeoutDoSomething function will execute thedoSomethingAsync function. If doSomethingAsync takes longer than 2000() => "Timeout"
milliseconds to resolve, the fallback function will be called
instead and its result will be returned.
This function takes in an array of functions fs and returns a new function.fs
The returned function takes the same parameters as the first function in fs
and applies each function in to those parameters. The result is an array offs
the return values from each function in .
#### Example
`typescript
const add = (a: number, b: number) => a + b;
const subtract = (a: number, b: number) => a - b;
const multiply = (a: number, b: number) => a * b;
const juxtFunc = juxt(add, subtract, multiply);
console.log(juxtFunc(5, 3)); // [8, 2, 15]
`
In this example, juxtFunc is a function that takes two parameters 5 and 3,add
and applies each of the three functions , subtract, and multiply to[8, 2, 15]
those parameters. The result is an array .
`ts`
(pairRight: Function) => (x: Parameters
This function takes a Function as its parameter and returns a new functionx
that takes an argument of the same type as the parameter of the original
function and returns a Promise that resolves to an array containing two
functions.
The first function in the array takes the same argument x and returns x. Thex
second function in the array takes the same argument and applies it to thef
original function , returning the result.
Example
`ts
const increment = (num: number) => num + 1;
const pair = pairRight(increment);
const result = pair(5);
console.log(result);
// Output: [5, 6]
`
In the above example, the pairRight function is used to create a new functionpair by passing in the increment function. When pair is called with an5
argument of , it returns an array [5, 6] where 5 is the original argument6
and is the result of applying increment to 5.
`typescript`
/**
* stack - Compose multiple functions together, passing the output of one function as the input to the next function.
*
* @param {...functions} - The functions to be composed.
* @returns {(_: { [i in keyof Functions]: Parameters
*/
stack
...functions: Functions
): (
_: { [i in keyof Functions]: Parameters
) => JuxtOutput
Example
`typescript
const add = (a: number) => (b: number) => a + b;
const multiply = (a: number) => (b: number) => a * b;
const stackFunctions = stack(add(2), multiply(3));
const result = stackFunctions({ _: 4 });
console.log(result); // { _: 4, plus: 6, times: 12 }
`
In the example above, the stack function is used to compose two functions -add and multiply. The resulting composed function takes an object as input,_
where the key corresponds to the input for the first function (add in this_
case). The composed function returns an object with the same input key () andplus
additional keys and times, representing the output of each function in{ _: 4 }
the composition. The example demonstrates calling the composed function with an
input of and logging the output { _: 4, plus: 6, times: 12 }.
#### Signature
`typescript`
#### Description
juxtCat is a utility function that takes in multiple functions as arguments
and returns a new function. This new function applies the arguments to each of
the input functions and then concatenates the results together.
#### Parameters
- ...fs: Functions : A rest parameter that accepts an array of functionsFunctions
() as input.
#### Return Value
The return value of juxtCat is a new function that takes in the same argumentsFunctions
as the first function in the input array, and returns the result
type of the first function.
#### Example
`typescript
const add = (a: number, b: number) => a + b;
const multiply = (a: number, b: number) => a * b;
const juxtMultipliedTotal = juxtCat(add, multiply);
console.log(juxtMultipliedTotal(2, 3)); // Output: [5, 6]
`
In the example above, juxtMultipliedTotal is a new function that takes two2
arguments ( and 3) and applies them to both the add and multiply[5, 6]
functions. The result is an array , which is the concatenation of the
results of the two functions.
Signature:
`typescript`
(..._: Parameters
Functions extends AnyAsync
The alljuxt function takes in an arbitrary number of functions as arguments...fs: Functions
() and returns a new function that accepts the same arguments..._: Parameters
as the first function (). This new function thentrue
applies each of the input functions to the arguments and returns a boolean value
indicating if all the functions returned .
If the input functions contain at least one asynchronous function
(Functions extends AnyAsync), the returned function will bePromise
asynchronous and return a . Otherwise, it will be synchronousboolean
and return a .
Example
`typescript
const isEven = (n: number) => n % 2 === 0;
const greaterThanThree = (n: number) => n > 3;
const checkNumber = alljuxt(isEven, greaterThanThree);
console.log(checkNumber(6)); // Output: true
console.log(checkNumber(2)); // Output: false
console.log(checkNumber(5)); // Output: false
`
In the example above, the alljuxt function is used to create a new functioncheckNumber. checkNumber accepts a number as an argument and applies theisEven and greaterThanThree functions to the argument. It returns true iftrue
both functions return , otherwise it returns false.
`typescript`
anyjuxt
The anyjuxt function takes multiple functions as arguments and returns a newfs
function. This new function takes the same arguments as the first function in
the array.
If any of the functions in the fs array returns true when called with theanyjuxt
provided arguments, then the function returns true. Otherwise, itfalse
returns .
If any of the functions in the fs array is an asynchronous function, theanyjuxt function returns a promise that resolves to either true or false
depending on the results of the asynchronous functions.
#### Example
`typescript
const isEven = (n: number) => n % 2 === 0;
const isPositive = (n: number) => n > 0;
const hasEvenOrPositive = anyjuxt(isEven, isPositive);
console.log(hasEvenOrPositive(4)); // true
console.log(hasEvenOrPositive(-3)); // true
console.log(hasEvenOrPositive(7)); // false
`
In this example, the hasEvenOrPositive function checks if a number is even oranyjuxt
positive. It uses the function to combine the isEven andisPositive functions. When called with 4, which is even, the anyjuxttrue
function returns . When called with -3, which is positive, the anyjuxttrue
function also returns . When called with 7, which is neither even noranyjuxt
positive, the function returns false.
`typescript`
withLock(
lock: () => void | Promise
unlock: () => void | Promise
f: Function,
): (...args: Parameters
The withLock function takes in three parameters: lock, unlock, and f. Itf
returns a new function that can be invoked with arguments. This new function
wraps the execution of inside a lock.
- lock: A function that acquires a lock. It can be either synchronous orvoid
asynchronous and should return or Promise.
- unlock: A function that releases the lock. It can be either synchronous orvoid
asynchronous and should return or Promise.
- f: The function that is being locked. It can be any asynchronous or
synchronous function.
The returned function can be called with any number of arguments that ff
expects. The wrapped execution of is guarded by the acquired lock. If anf
exception is thrown within , the lock is still released before re-throwing
the exception.
Example
`typescript
const lock = () =>
new Promise
console.log("Acquiring lock...");
setTimeout(resolve, 1000);
});
const unlock = () => {
console.log("Releasing lock...");
};
const processResource = async (resource: string) => {
console.log(Processing resource: ${resource});Finished processing resource: ${resource}
// Simulate some work being done
await new Promise((resolve) => setTimeout(resolve, 2000));
console.log();
};
const lockedProcessResource = withLock(lock, unlock, processResource);
lockedProcessResource("example.com");
// Output:
// Acquiring lock...
// Processing resource: example.com
// Finished processing resource: example.com
// Releasing lock...
`
In the example above, the withLock function is used to create a wrappedprocessResource
version of the function. The lock is acquired beforeprocessResource is executed and released after it completes. This ensures thatprocessResource
only one instance of can be executing at a time, preventing
race conditions when accessing shared resources.
This function retries executing a given function until it returns a truthy
value. It waits for 50 milliseconds between each execution.
#### Parameters
- f: A function that returns a boolean or a Promise that resolves to a
boolean. This function is executed repeatedly until it returns a truthy value.
#### Return Value
A Promise that resolves to void.
#### Example
`javascript
async function testFunction() {
let counter = 0;
const f = () => {
counter++;
return counter > 3;
};
await retry(f);
console.log("Success!"); // Output: Success! after 4 retries
}
testFunction();
`
Creates a lock function that can be used to lock resources with a given ID.
#### Signature
(set: (_: Key) => boolean | Promise
#### Parameters
- set: (_: Key) => boolean | Promise: A function that sets the locktrue
status for a given ID. It should return if the lock is successfullyfalse
set, otherwise, or a Promise resolving to either of these values.
#### Return Type
(id: Key) => Promise: A function that locks a resource with the given
ID.
#### Example
`
const setLock = (id) => {
// Perform logic to set the lock for the given ID
// Return true if the lock is successfully set, false otherwise
};
const lockResource = makeLockWithId(setLock);
const resourceId = 'resource1';
lockResource(resourceId)
.then(() => {
console.log(Resource ${resourceId} locked);Failed to lock resource ${resourceId}
// Perform actions with the locked resource
})
.catch((error) => {
console.error(, error);`
});
In the above example, we have a function setLock that sets the lock status formakeLockWithId
a given ID. We then use the function to create a lockResourcesetLock
function that can be used to lock resources by their IDs. We pass the makeLockWithId
function as the argument to . We can then use the lockResource
function to lock a specific resource by its ID, and perform actions with the
locked resource once it is successfully locked.
The withLockByInput function wraps an asynchronous function f with a lock
using dynamic lock ID based on input arguments.
#### Signature
`typescript`
withLockByInput
argsToLockId: (..._: Parameters
lock: (_: string) => Promise
unlock: (_: string) => Promise
f: Function,
): (...args: Parameters
#### Parameters
- argsToLockId - A function that takes input arguments of f and returns alock
string representing the lock ID.
- - A function that takes a lock ID and locks it.unlock
- - A function that takes a lock ID and unlocks it.f
- - The asynchronous function to be wrapped with a lock.
#### Return Value
A function that takes the same input arguments as f and returns a promise thatf
resolves to the result of after acquiring and releasing the lock.
#### Example
`typescriptlock-${x}-${y}
const argsToLockId = (x: number, y: number) => ;
const lock = (lockId: string) => Promise.resolve();
const unlock = (lockId: string) => Promise.resolve();
const add = async (x: number, y: number) => {
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulating asynchronous operation
return x + y;
};
const wrappedAdd = withLockByInput(argsToLockId, lock, unlock, add);
wrappedAdd(2, 3).then(console.log); // Output: 5 (after a 1 second delay)
`
In the example above, the wrappedAdd function is created usingwithLockByInput by providing the lock ID generator function argsToLockId,lock
the lock function , the unlock function unlock, and the async functionadd. When wrappedAdd is called with arguments (2, 3), it acquires a locklock-2-3
with the lock ID , executes the add function, waits for it to5
complete, releases the lock, and returns the result .
`typescript`
(
(...args: Parameters
The sequentialized function takes an asynchronous function f and returns af
new function that ensures that all invocations of are processed in a
sequential order. It achieves this by creating a queue of pending invocations
and processing them one by one.
#### Example
`typescript
async function asyncFunction(num: number): Promise
return new Promise((resolve) => {
setTimeout(() => {
resolve(num * 2);
}, 1000);
});
}
const sequentializedAsyncFunction = sequentialized(asyncFunction);
sequentializedAsyncFunction(2); // Invokes asyncFunction(2)
sequentializedAsyncFunction(4); // Waits for the previous invocation to complete before invoking asyncFunction(4)
sequentializedAsyncFunction(6); // Waits for the previous invocation to complete before invoking asyncFunction(6)
`
In this example, asyncFunction is an asynchronous function that takes a number
and returns a promise that resolves to the double of the number after a delay of
1 second.
The sequentializedAsyncFunction is obtained by calling the sequentializedasyncFunction
function with . When called multiple times, it ensures that each
invocation is added to a queue and processed sequentially.
In the example, sequentializedAsyncFunction(2) is called first, and it startssequentializedAsyncFunction(4)
processing immediately. Then is called whilesequentializedAsyncFunction(6)
the first invocation is still running, so it is added to the queue. Finally, is called while both previous invocations are
still running, so it is also added to the queue.
Once the first invocation completes, the result is resolved and the second
invocation is started. Similarly, when the second invocation completes, the
result is resolved and the third invocation is started. This ensures that the
invocations are processed in the order they were made, even though each
invocation has a delay of 1 second.
throttle is a higher-order function that limits the maximum number of parallel
invocations of an asynchronous function.
#### Signature
`typescript`
throttle
#### Parameters
- maxParallelism : number - The maximum number of parallel invocationsf
allowed.
- : Function - The asynchronous function to be throttled.
#### Returns
A throttled version of the function f. The throttled function has the samef
signature as , and returns a promise that resolves to the result of invokingf.
#### Example
``typescript
const asyncFunction = async (arg: number) => {
// Simulating an asynchronous operation
await new Promise((resolve) => setTimeout(resolve, 1000));
return arg * 2;
};
const throttledFunction = throt