README
TS Utilities
This is a repo with utility functions and definitions for TypeScript projects. This is more or less an in-house library, but free to use for anybody.
Table of Contents
- Table of Contents
- Installation
- Base Types
- Function Typings
- Functions Namespace
- Enum Namespace
- Objects Namespace
- Futures Namespace
- Latchable Class
- Lockable Class
- Backoff Class
- Decorators
Installation
Install with NPM or Yarn
npm i @nolawnchairs/utils
or
yarn add @nolawnchairs/utils
Base Types
KeySet<T>
Represents a set of keys from an object of type T
, Useful for giving intellisense a helping hand.
interface Foo {
bar: string
baz: number
quux: boolean
}
function thing(keys: KeySet<Foo>) { ... }
// intellisense will offer 'bar' | 'baz' | 'quux'
WithLength
Represents an object that has a length
property
function thing(object: WithLength) {
object.// intellisense will offer 'length'
}
Nullable<T>
Represents a value that can possibly be null
or undefined
. Useful for annotating function arguments or return values that may not exist.
function thing(value: Nullable<string>) { ... }
Function Typings
Function typings are a succinct way of defining functions, and make documenting more clear. Most these names are analagous to the functional interfaces of Java 8, with additional typings that are specific to JS/TS development.
// Normally, you'd define a function argument like this
function foo(callback: (value: string) => string) { ... }
// That's hard to read with intellisense. The above can be written like this:
function foo(callback: UnaryOperator<string>) { ... }
VoidFunction
Represents a function that takes no arguments and returns no data.
() => void
Function<T, U>
Represents a function that accepts one argument T
and produces a result U
.
(value: T) => U
NullableFunction<T, U>
Represents a function that accepts one argument T
and produces a result U
that may be null or undefinded.
(value: T) => Nullable<U>
OptionalFunction<T, U>
Represents a function that accepts one optional/nullable argument T?
and produces a result U
.
(value?: T) => U
BiFunction<T, U, R>
Represents a function that accepts two arguments T
, U
and produces a result R
.
(value1: T, value2: U) => R
NullableBiFunction<T, U, R>
Represents a function that accepts two arguments T
, U
and produces a result R
that may be null or undefined.
(value1: T, value2: U) => Nullable<R>
OptionalBiFunction<T, U, R>
Represents a function that accepts two optional arguments T?
, U?
and produces a result R
.
(value1?: T, value2?: U) => R
Supplier<T>
Represents a supplier of a result T
.
() => T
NullableSupplier<T>
Represents a supplier of a result T
that may be null or undefined.
() => Nullable<T>
AsyncSupplier<T>
Represents a supplier of a promise of type T
. Analagous to Supplier<Promise<T>>
() => Promise<T>
NullableAsyncSupplier<T>
Represents a supplier of a promise of type T
whose resolved value maybe null or undefined. Analagous to NullableSupplier<Promise<T>>
() => Promise<Nullable<T>>
BiSupplier<T, U>
Represents a supplier of two results as a tuple of [T, U]
.
() => [T, U]
Consumer<T>
Represents an operation that accepts a single input argument T
and returns no result. Useful for defining event handlers
(value: T) => void
OptionalConsumer<T>
Represents an operation that accepts a single optional/nullable input argument T
and returns no result.
(value?: T) => void
BiConsumer<T, U>
Represents an operation that accepts two input arguments T
, U
and returns no result.
(value1: T, value2: U) => void
OptionalBiConsumer<T, U>
Represents an operation that accepts a optional arguments, T
, U
and returns no result.
(value1?: T, value2?: U) => void
UnaryOperator<T>
Represents an operation on a single operand that produces a result of the same T
type as its operand. Analagous to Function<T, T>
(value: T) => T
BinaryOperator<T>
Represents an operation upon two operands of the same type T
, producing a result of the same type as the operands. Analagous to Function<T, T, T>
(value1: T, value2: T) => T
Predicate<T>
Represents a predicate (boolean-valued function) of one argument T
.
(value: T) => boolean
BiPredicate<T, U>
Represents a predicate (boolean-valued function) of two arguments T
(value1: T, value2: U) => boolean
Comparator<T>
Represents a comparison operation of two operands of type T
. Analagous to BiFunction<T, T, number>
. Useful for custom sorting operations.
(value1: T, value2: T) => number
EqualityOperator<T>
Represents an operation that compares two operands of the same type T
and produces a boolean result for comparison purposes. Analagous to BiFunction<T, T, boolean>
(value1: T, value2: T) => boolean
Functions namespace
The function namespace contains a few miscellaneous utility functions that shorten some verbose native JS/TS implentations
Functions.coerceBoolean
(value: any) => boolean
Takes a value of any
type and coerces it into a boolean value. If a string is passed, the function will return true if the string equals a case-insentive true
, othwerwise it will return false. If any other type besides String
is passed, a double-negation operation is performed on the operand. This is useful for use in lambda functions to keep code more succinct. Extrememly useful for filtering falsey values from arrays.
// Standard way that doesn't account for a string value of `true`
foo('bar', x => !!x)
// Standard way that accounts for a string value of `true`
foo('bar', x => typeof x === 'string' ? x.toLowerCase() === 'true' : !!x)
// Messy. Use coerceBoolean
foo('bar', Functions.coerceBoolean)
Functions.isTruthy
(value: any) => boolean
Returns true if a value evaluates to true
after double negation. Analagous to using (x => !!x)
, but represents a cleaner and more easy to read declarative code style.
Functions.isFalsey
(value: any) => boolean
Returns true if a value evaluates to false
after single negation. Analagous to using (x => !x)
, but represents a cleaner and more easy to read declarative code style.
Functions.noop
() => void
A function that takes no arguments, and produces no result. Useful for times when you're required to pass a function argument, but don't care about the result.
Functions.noopAsync
() => Promise<void>
As with noop
, this function takes no arguments, but produces a Promise
of type void
that immediately resolves. Not especially useful, except to appease the Typescript compiler in certain cases where you don't care about an asynchronous result, but still must pass an asynchronous function consumer.
Enum namespace
Enums in Typescript are very useful, but come with some restrictions out of the box when it comes to introspection. The Enum
namespace exposes functions to read keys and values from your enum objects, and gather additional information about them that are otherwise verbose to write yourself.
Enum.keys
(anEnum: object) => string[]
Gets the keys (labels) of an enum as an array of strings
// String values
enum Color {
Red = '#FF0000',
Green = '#00FF00',
Blue = '#0000FF'
}
Enum.keys(Color)
// ['Red', 'Green', 'Blue']
Enum.values
<T>(anEnum: object) => T[]
Gets the values of an enum, either an array of strings or numbers.
// String values
enum Color {
Red = '#FF0000',
Green = '#00FF00',
Blue = '#0000FF'
}
Enum.values(Color)
// ['#FF0000', '#00FF00', '#0000FF']
// Sequential numeric values
enum Status {
Pending,
Resolved,
Rejected
}
Enum.values(Status)
// [0, 1, 2]
// Defined numeric values
enum StatusCode {
OK = 200,
BadRequest = 400,
NotFound = 404,
ServerError = 500
}
Enum.values(StatusCode)
// [200, 400, 404, 500]
Enum.toObject
<T>(anEnum: object, keysAs?: Enum.Type) => T[]
Returns an object representing the enum. For enums with string values, the enum is returned as-is, as it is already an object. Since enums with numeric values are keyed both by label -> int
and int -> label
, the expected key type keysAs
argument can be provided. This defaults to Enum.Type.Number
.
// String values
enum Color {
Red = '#FF0000',
Green = '#00FF00',
Blue = '#0000FF'
}
Enum.toObject(Color)
// { Red: '#FF0000', Green: '00FF00', Blue: '#0000FF' }
// Numeric values
enum Status {
Pending,
Resolved,
Rejected
}
Enum.toObject(Status) // Enum.Type.Number as default
// { 0: 'Pending', 1: 'Resolved', 2: 'Rejected' }
Enum.toObject(Status, Enum.Type.String)
// { Pending: 0, Resolved: 1, Rejected: 2 }
Enum.length
(anEnum: object) => number
Returns the length (count) of the enum key/value pairs
// String values
enum Color {
Red = '#FF0000',
Green = '#00FF00',
Blue = '#0000FF'
}
Enum.length(Color)
// 3
Enum.isNumeric
(anEnum: object) => boolean
Returns true if an enum is of numeric values
enum Color {
Red = '#FF0000',
Green = '#00FF00',
Blue = '#0000FF'
}
enum Status {
Pending,
Resolved,
Rejected
}
Enum.isNumeric(Color) // false
Enum.isNumeric(Status) // true
Enum.isSequential
(anEnum: object) => boolean
Returns true if an enum is of numeric values and the numeric values are zero-based and sequential
enum Status {
Pending,
Resolved,
Rejected
}
enum Flags {
FlagOne = 1,
FlagTwo = 2,
FlagThree = 4,
FlagFour = 128
}
Enum.isSequential(Status) // true
Enum.isSequential(Flags) // false
Objects namespace
The Objects namespace offers some useful utility functions for filtering out undesirable properties from your POJOs.
Objects.nonNull
<T = Record<string, any>>(input: T) => Partial<T>
Removes all object properties that are null
or undefined
, but retains falsey values such as empty strings or zero.
Objects.nonUndefined
<T = Record<string, any>>(input: T) => Partial<T>
Remove all object properties that are undefined, but retains null properties and falsey values such as empty strings or zero
Objects.truthy
<T = Record<string, any>>(input: T) => Partial<T>
Remove all object properties that are considered "falsey", which includes null, undefined, zero, false and empty strings, but retains empty arrays and objects.
Objects.nonEmptyStrings
<T = Record<string, any>>(input: T) => Partial<T>
Remove all object properties that are empty (zero-length) strings.
Objects.drop
<T extends Record<string, any>, K extends keyof T>>(input: T, ...keys: K[]) => Partial<T>
Removes specified properties by provided keys.
const a = {
first: 1,
second: 2,
third: 3,
fourth: 4,
fifth: 5,
}
const b = Objects.drop(a, 'first', 'third')
console.log(b)
// {
// second: 2,
// fourth: 4,
// fifth: 5,
// }
Objects.mapEntries
<K extends Keyable, V, W = keyof K>(input: Record<K, V>, cb: BiFunction<Keyable, V, W>): Record<K, W>
Map the result of a function to each entry's value in the object.
const a = {
one: 1,
two: 2,
three: 3,
four: 4,
five: 5,
}
const b = Objects.mapEntries(a, (k, v) => v * 2)
console.log(b)
// {
// one: 2,
// two: 4,
// three: 6,
// four: 8,
// five: 10,
// }
Futures namespace
The Futures
namespace contains functions that wrap the nastiness of setTimeout
and allow cleaner code geared towards usage alongside async/await
.
async Futures.wait
async (ms: number) => Promise<void>
An asynchrounous function that waits for a given number of milliseconds ms
and resolves. This is a convenience wrapper around setTimeout
async function foo() {
console.log('Before')
await Futures.wait(1000)
console.log('This prints one second later')
}
async Futures.delayed
async (ms: number, callback: VoidFunction) => Promise<void>
Delays execution of a function callback
for a given amount of milliseconds ms
. This is a Promise-based wrapper around setTimeout
. Internally, this calls Futures.wait
, then executes the callback function. Useful when using inside functions that can't be async, such as React's useEffect
hook.
function foo() {
console.log('Before')
Futures.delayed(1000, () => console.log('This prints one second later'))
}
async Futures.waitUntil
async (condition: AsyncSupplier<boolean>, waitInterval?: number) => Promise<void>
For usage in an async
function, this will delay progression of the async function until the boolean result of condition
resolves to true. If the supplier never resolves, the function will not continue. Internally, this uses setInterval
to check the truthiness of the supplier's outcome. The default interval frequency is 1
millisecond, but can be changed to a more lazy frequency by passing a millisecond value to waitInterval
.
async function foo() {
console.log('Before')
await Futures.waitUntil(() => resourcesAreLoaded())
console.log('This will print when resources are loaded')
}
async Futures.waitUntilResolved
async (timeout: number, condition: AsyncSupplier<boolean>, waitInterval?: number) => Promise<void>
This does the same thing as Futures.waitUntil
, except that it takes a timeout
in milliseconds as the first agument and will reject if the condition is not met before the timeout expires. Use inside of a try/catch
block.
async function foo() {
console.log('Before')
try {
await Futures.waitUntilResolved(2000, () => resourcesAreLoaded())
console.log('Resources loaded within 2 seconds')
} catch {
console.error('Resources were not loaded within 2 seconds')
}
}
async Futures.awaitWithTimeout
async <T>(timeout: number, runner: AsyncSupplier<T>) => Promise<T>
For usage in an async
function, this calls an async supplier function and will attempt to return its resolved value within the allotted timeout. If the function does not resolve before the timeout expires, an error is thrown. Use inside of a try/catch
block.
try {
const value = await Futures.awaitWithTimeout<any>(10000, () => somePromiseThatMayNeverResolve())
console.log('It resolved, we got the value!')
} catch {
console.error('It never resolved, so timed out after 10000ms 💩')
}
Latchable Class
class Latchable<T>
Produces an object that will latch onto a value of type T
, and will lock the value, preventing it from being modified. Useful for a value that needs to be defined, but populated later, but never redefined in the future.
constructor
constructor(value?: T)
The constructor takes an optional value of its type T
. If a value is provided, the value will be locked (latched) immediately, otherwise the value will be locked when latched.
static Latchable.immutable
Latchable.immutable<T>(value?: T)
Creates a Latchable where the value will be frozen when supplied. If the value is supplied here via Latchable.immutable
, it will lock and freeze immediately, otherwise it will lock and freeze the value when latched.
Latchable.latch
(value: T) => void
Latches the value of type T
and locks the instance. If latch
is called while the instance is locked, an error is thrown.
const holder = new Latchable<number>()
try {
doSomethingThatMayThrowAnError()
holder.latch(1)
} catch {
holder.latch(2)
}
Latchable.value
A getter for the current latched value
const holder = new Latchable<number>()
...
holder.latch('foo')
console.log(holder.value) // 'foo'
Backoff Class
class Backoff
Creates a backoff circuit where a user-defined function is called after an increasingly delayed time. All time values with the Backoff class are measured in seconds
constructor
constructor(options: BackoffOptions)
BackoffOptions
Property | Type | Description |
---|---|---|
strategy |
BackoffStrategy |
The strategy to govern the time between calls |
callable |
AsyncSupplier<boolean> |
The function to call on each tick. It must return a Promise of type boolean where true indicates the operation has completed and the circuit can stop, and false indicates the circuit must continue and triggers another wait period |
maxWait |
number | The maximum time (in seconds) to wait before the circuit times out. Optional |
maxInterval |
number | The maximum time (in seconds) to wait between circuits. Optional |
initialDelay |
number | The time (in seconds) to wait before the first circuit. Optional |
onTick |
VoidFunction |
A function that will be called on each circuit. Optional |
Backoff.start
() => Promise<void>
Starts the backoff circuit. Note that if the circuit times out, and error will be thrown, so ensure you handle the promise rejection.
enum BackoffStrategy
Value | Description |
---|---|
LINEAR |
Subsequent calls are made in an incremental fashion, e.g. 1, 2, 3, 4, 5... |
FIBONACCI |
Subsequent calls are made using the Fibonacci sequence, e.g. 1, 2, 3, 5, 8, 13... Note this is not true Fibonacci, as the sequence starts 1, 2 and not 1,1 |
EXPONENTIAL |
Subsequent calls are made doubling the last, e.g. 1, 2, 4, 8, 16... |
Decorators
@Bind
Binds the decorated method's this
context to the instance of the class to which it's attached
class Sample {
public value = 'Value'
@Bind()
getValue(): string {
return this.value
}
}
function someFunction(fn: Consumer<string>) {
console.log(fn())
}
const sample = new Sample()
// Will print 'Value' because the getValue method is bound to the instance
someFunction(sample.getValue)