Last active
September 3, 2024 20:37
-
-
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.
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
// 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