Skip to content

Instantly share code, notes, and snippets.

@asleepace
Last active September 3, 2024 20:37
Show Gist options
  • Save asleepace/ce601513215149d95496478256ac1333 to your computer and use it in GitHub Desktop.
Save asleepace/ce601513215149d95496478256ac1333 to your computer and use it in GitHub Desktop.
A simple extension for TypeScript which enables the `.try(args)` method on functions. This works for both normal & async functions, and reduces a lot of boilerplate.
// Try.ts
// By Colin Teahan
// August 14th, 2024
//
// This TypeScript file enables usage of the .try() method which will return a result tuple of [T?, Error?]
// from any function or async function. This works by defining a new property on the global Object.prototype
// and then do some type-fu.
//
// Example usage:
// https://www.typescriptlang.org/play/?target=99#code/MYewdgziA2CmB0w4EMBOAKAlAKGwejwAJCAVVAT3gBcJ8jCAhcwgYRgEsxTZkALZMHWIBBAK4BzURCqEAjABYqvADSEATAAY18ukNK92EUuQAOsAMrBU7EzIBm7OIVhhkAIzhGpycbEIg7QiU-agosQgBbWCUQABNCAHcDYF5Ex2hCVGjRVC5kTNgIUWgZKlETJwDCAG0SAH5VAFFUVBBUOoBdPTtWiMIBZjtRMGAqdnB-VH6IchHCIZGx8Hh9Q0S2gGsjN2ZY2AcwTnF+wjBYBMITVrNUKmYJ4MJxaBA3ZAyAeTcAK1hR+CuICoQNMsD0AniwS4sRAhCgUSCoIAtEN4LoCMRGgAPZARCp+by+ABc6PoC1G4y4vioAFUILBUEwAJKxdDsWJE06iCJuBmYQgAbz0xGI7ECbPiAB4ALyEDT8pStC5nC7NVoYADknAAbu92YQpAzCOyNTgMSLiFkyrlBadcbBORqGK8Nap9QBfPSegh6UCQGTVQ2oVQM9UdQiy6l0hnM2KhcjoeWESVIpHOFptNLQDK8grWs6xX3gaQ1IMhjOocOR6LRxnkFnx9CyfkptNBwhOl3GoxWnIF0nEAByHxIjU5JAMRjW0nSJwSm2NXEB4iyECMEO7pyBcNE7Co7icdkzgNiogpy0I5k4wD8SjW89QWzh7AijjQ0GYwMIvBACT0j0Qd5oHCTN4GQEwKgTO8IH5KIYliIwHw2N0ZDWMBt0kNAXCoWBYEhXg-A1aCNX6VBJCiMAZASWd3gSZByG2PxQBaP4qDRH1cE4HDUDsZAb1ICgD1gAAxYZzzASVhFUEgAD5BWwC10HgZS0HECBOWEaoOkwcdCAAH1OWBtQZBSRWAID0GgzkBlUZSwLI9TCE07TdIMs5jNQbBPWwLiGV4-jhBmEYyHIITRMWSlJOkuShQtZAguAQglJUhyNK0nTCAABV6QxYElWTTOIczs0sydrLAchbJStS0pcrKcvpfKZK83A7jMATQo8PLhAc6KIw6sKxKWCSerUvqDMC2ZgBCwaIvASTetIZqhAnEIqDCWDoh-eJe1ydcgnKSpAlqVRhj2A48PDTNAzAc7ODwpoKw6VQdkIPYkDQI5jVKSdEnooRHjavwZmkWA+jWISglhPZT34pRkBkEhNyuQpsONQI1UzNYzv2e74g3bVDCRDyIGQNEgYGrrzDPG81xIQ68tk-qToNW7cYLLoKZmrrhOQRwclgen8UlNV+sx1A5NlG67oLR7K2wLnBK6gAlQpiioIW4Ca-rubgangFpiBNcZuSDN1kS+egAXjdwDFsRw26jEedDUAid5CHC8TF24-zb1ha8rb2IICMIRtNvg1RoM3b4pHsTNAdBOEGJwiJlCEDdgiyUjb3nKH1kfNIlEINwgVSeKpv6W6tzAJFy7mODtogNFfJ4vi-E94b5ItdbyHy1RhGcLEHYQyvyC0mTSsMTlkvsmqnP5aU5OykBX0a2SqtnxzhAy5fV8ZpW4FVooSmNprmu7ig+6cwfh-XCrx8nxyZ9UreF7kkgN5fjSMvNo-1dPgq3kMQAAlYDQBuPMIalI84pD+BsdGJxySdzWJNOYmZ0JUBQhqIwJgQBrnYF1YOCNGDuDAYQcyXAohrgNCYb8v4Aa-RADcBGhRVBQESH4QEhMg5UFzsgWIsQ9yUndrA4AWw0RIOgYYXeuUopLXQKudWnJhgbHQgkMAGVFElE3DIteMVCp5j7IwEAMAeBgAUWrbRAAyKxiIzBVC0TIaUziOyvF+KMEiNiOxQhIpwAox8ZBeI1OZKgKRfFcEcTgIBRAZa3hDh3aBREKAkQbnERIyQy5UBTrYJ2sJioZEeKk-GVddp5H8erA6+J2JEGHKOTkTJAiPEkRMCGCUOGGTwnnUpwdgY00KE7Bmkx5iWwFpUpwfjghCEBHvch-A-HsL3OQgQxc-DIDonuPCaIvjuLYrE5eNw7joG2axAErRgRA1UEk8grou4igSNYfcXVOTrVELANOFo-QOEkKgISzzUCvPeSKFw3IGS-KCP8t5BjdRWwdJAuaI1oqP3HAfbq0VP6pSculZFnVD6WI1gzbWBldH7xxbAP+J8CWyX0RaC+zBYo0oZX6Esjj+rQUAiVKO8VR7orUjgBlDKxRJWkQ1WAFiAmYH5PS-l-LumOIMdK6V1ACLmLFW-Goq5TpswurEcMXLzb60NgAmSfKFWmsAqE3g6B0ChjVdLdmD10yVmmJTOAvN+ZZGNia017pnDQHpLc01FpunVEcZq2JOrnX6r6XTSl59A2emlT6kJKQkqhjaJK+VMrsg2jtdq8s6pnXi11UYc2bqrYeoZpm4gCaRSendDgW2RBhCxF1CMTpsAcR4icNRIuddErNMgLgPtcKvZ+jOKMcIUqEHoAALII14PAH5t0V7hElHKeAABWBUvAlSEAAESiFSCAXgABCPdBjunEsXYUGAxl0DBPAOOnCsQT2mhatgAdlwRUMHinhSdF7s1cBVPVFeuUrWrlvW8goOy1VTotIK2d87F0QhXS2ddW7oOsXQHu0MLQ8MtD3V6mlYD-UQegHevdYBuS8lQNKPdhAADUhA51KGQ8uiIWAiP1vfdgYdn63acH-R84sAZpAIykPmtoVZ+jrJkGOk5PcsAGKZaY+ALxxDoAFHCfcZQICSamNx5TIn1WsMddJtZfMZDTNyj++kcZFNEZU3ANTIANNaY1Y6wg3HvK4GwAJ8xmAgA
//
// function getUserById(id: number) {
// if (id <= 0) throw new Error('invalid user id')
// return { name: 'Bob', id }
// }
//
// const [user, error] = getUserById.try(0) <-- error will be returned
// const [user, error] = getUserById.try(1) <-- user 'Bob' is returned
//
// NOTE: This is still a work in progress and is not suitable for production. Since this works similarly to how
// the .call() or .apply(this) methods work, it is not guarenteed the 'this' argument will always be correct.
//
interface TryableFunction<A, T> {
(...args: A[]): T | never
call(this: any, ...args: A[]): T | never
}
interface AsyncTryableFunction<A, T> {
async (...args: A[]): Promise<T>
call(this: any, ...args: A[]): Promise<T>
}
type Tryable<Args, T> = TryableFunction<Args, T> | AsyncTryableFunction<Args, T>
// The .try() method returns a tuple of [T, undefined] or [undefined, Error], by declaring it this way
// the type system is able to deduce that T is present if Error is undefined and vis-versa.
type TryableSuccessTuple<T> = [T, undefined]
type TryableFailureTuple<Err = Error> = [undefined, Err]
type TryableResultTuple<T> = TryableSuccessTuple<T> | TryableFailureTuple
// Extends the normal Function interface to include the .try() method, this is just for the type system,
// and there are two to work with both async and non-async methods.
interface Function {
try<T, A extends any[]>(this: (...args: A) => Promise<T>, ...args: A): Promise<TryableResultTuple<T>>
try<T, A extends any[]>(this: (...args: A) => T, ...args: A): TryableResultTuple<T>
}
// Helper function to check if a function is Async or not, it's possible that Babel can mess up how
// this operates, so we provide two additional checks.
function isPromise<A, T>(result: unknown): result is Promise<T> {
return Boolean(result && typeof result === 'object' && 'then' in result && 'catch' in result)
}
// define the Function 'try' method which attempts to call the method and return a result tuple.
// NOTE: If the function is async we need to return the success tuple or failure tuple in the
// promise chain so it can be awaited.
Object.defineProperty(Object.prototype, 'try', {
writable: true,
configurable: true,
enumerable: true,
value: function<A, T>(this: Tryable<A, T>, ...args: A[]): TryableResultTuple<T> | Promise<TryableResultTuple<T>> {
try {
const result = this.call(this as any, ...args)
if (isPromise(result)) {
return result
.then((res) => [res, undefined] as TryableSuccessTuple<T>)
.catch((err) => [undefined, err] as TryableFailureTuple)
} else {
return [result, undefined] as TryableSuccessTuple<T>
}
} catch (error) {
return [undefined, error as Error] as TryableFailureTuple
}
}
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment