Installations
npm install @ts-to-go/wrap-exception
Developer Guide
Typescript
Yes
Module System
CommonJS, ESM
Min. Node Version
>=16
Node Version
20.17.0
NPM Version
10.8.2
Releases
Contributors
Unable to fetch Contributors
Languages
TypeScript (95.9%)
JavaScript (4.1%)
Developer
sergioflores-j
Download Statistics
Total Downloads
422
Last Day
1
Last Week
5
Last Month
15
Last Year
408
GitHub Statistics
11 Stars
48 Commits
1 Watching
6 Branches
3 Contributors
Bundle Size
1.17 kB
Minified
597.00 B
Minified + Gzipped
Package Meta Information
Latest Version
2.0.0-rc.3
Package Id
@ts-to-go/wrap-exception@2.0.0-rc.3
Unpacked Size
15.89 kB
Size
4.81 kB
File Count
4
NPM Version
10.8.2
Node Version
20.17.0
Publised On
09 Sept 2024
Total Downloads
Cumulative downloads
Total Downloads
422
Last day
0%
1
Compared to previous day
Last week
150%
5
Compared to previous week
Last month
275%
15
Compared to previous month
Last year
2,814.3%
408
Compared to previous year
Daily Downloads
Weekly Downloads
Monthly Downloads
Yearly Downloads
![Empty State](/_next/static/media/empty.e5fae2e5.png)
No dependencies detected.
@ts-to-go/wrap-exception
@ts-to-go/wrap-exception
is a wrapper function that encapsulates both asynchronous and synchronous functions to return a comprehensible object containing both the error and the result.
By adopting this error handling pattern, ts-to-go
allows for a more explicit management of error states, enhancing the readability of your TypeScript code as it allows for easier to write "early return" pattern.
Getting started
You can install the @ts-to-go/wrap-exception
package using any package manager:
1npm i @ts-to-go/wrap-exception 2# or 3yarn add @ts-to-go/wrap-exception 4# or 5pnpm add @ts-to-go/wrap-exception
PS: Bear in mind it needs to be a runtime dependency, so do not install it as a "devDependency".
How to Use
Basic example:
1const wrappedFunction = wrapException(_someAsyncFunctionThatThrowsOrRejects_); 2 3const { isError, error, data } = await wrappedFunction(); 4 5if (isError) { 6 // Handle the error here 7 console.error(error); 8} else { 9 // Data can be safely used here 10 console.log(data); 11}
TIP: always check if the result of a function execution is an error or not using the isError
attribute. (more about it in the FAQ section)
1. Wrapping Asynchronous Functions (simple usage)
1// An example of an async function 2const myAsyncFn = async (param: string) => { 3 if (param !== 'some-allowed-value') { 4 throw new Error('Oops! An error occurred.'); 5 } 6 7 await _doSomeAsyncProcessingThatCanThrow_(); 8 9 return { success: true }; 10};
1import wrapException from '@ts-to-go/wrap-exception'; 2 3// Wrap it using wrapException 4const wrappedAsyncFn = wrapException(myAsyncFn); 5 6// Use the wrapped function 7const { isError, error, data } = await wrappedAsyncFn('error'); 8 9if (isError) { // will be: true for this example 10 console.error(error); // will log: Error: 'Oops! An error occurred. 11} else { 12 console.log(data); // will not be reached as it was an error 13}
2. Wrapping Synchronous Functions (simple usage)
1// An example of a sync function 2const syncFn = (num1: number, num2: number) => { 3 if (num1 === 0) throw new Error('Zero is not allowed.'); 4 5 return num1 + num2; 6};
1import wrapException from '@ts-to-go/wrap-exception'; 2 3// Wrap it using wrapException 4const wrappedSyncFn = wrapException(syncFn); 5 6// Use the wrapped function 7const [error, result] = wrappedSyncFn(0, 1); 8 9if (isError) { // will be: true for this example 10 console.error(error); // will log: Error: 'Zero is not allowed.' 11} else { 12 console.log(data); // will not be reached as it was an error 13}
TIP: Wrapping any function by default
You can also adopt exporting functions wrapped by default:
1// src/utils/some-utility.ts 2import wrapException from '@ts-to-go/wrap-exception'; 3 4export const myAsyncFn = wrapException(async (param: string) => { 5 if (param !== 'some-allowed-value') { 6 throw new Error('Oops! An error occurred.'); 7 } 8 9 return 'Success'; 10});
Then in your "handler" you can consume this function already wrapped:
1// src/my-actual-handler.ts 2import { myAsyncFn } from 'src/utils/some-utility'; 3 4const { isError, error, data } = await myAsyncFn('will throw error'); 5 6// Now you can handle the error/response easily 7console.log(isError); // true 8console.log(error); // Error: 'Oops! An error occurred.' 9console.log(data); // undefined
This reduces complexity managing wrapException
instances in the code and enforces a standard for error handling in the whole project.
More examples
Usage Complexity Levels
Simple usage (RECOMMENDED)
It is important to note that the examples are mostly focusing in the "simple usage complexity level" of wrapException
.
This should cover most of the scenarios for every consumer as it also enforces the error handling to be more criterious and handle error types properly. As the error is also unknown
by the function's author.
1const someWrappedFn = wrapException(async () => { ... }); 2 3const { isError, error, data } = await someWrappedFn(); 4 5if (isError) { 6 if (isAxiosError(error)) { 7 // ... do something else... 8 } 9 if (error instanceof Error) { 10 // ... do something... 11 } 12 13 // unknown error 14 throw new Error('unknown error'); 15}
Advanced usage (USE WITH CAUTION)
wrapException
also can serve as a way to explicitly state what errors the function can throw. This is called "advanced - usage complexity level".
This can be useful if the function's author is sure what kinds of errors the function can throw.
1const myFn = async () => { ... }; 2const someWrappedFn = wrapException<typeof myFn, Error>(myFn); 3 4const { isError, error, data } = await someWrappedFn(); 5 6if (isError) { 7 // this will always be of type "Error" 8 throw error.message; 9}
Remark: Why advanced usage is "USE WITH CAUTION"?
Because having the static types for errors can be misleading.
If the function starts to throw other kinds of errors and the author forgot to update the type definitions, then consumers will not be prepared to handle those new errors and it might be that they break the error handling.
1const myFn = async () => { ... }; 2const someWrappedFn = wrapException<typeof myFn, AxiosError>(myFn); 3 4const { isError, error, data } = await someWrappedFn(); 5 6if (isError) { 7 /** 8 * If `myFn` starts to throw some other error type, such as: 9 * @example Error 10 * @example ValidationError 11 * @example SyntaxError 12 * ... 13 * Then `error.response` below will be undefined 14 * Breaking the previous implementation of the consumer, as like: 15 */ 16 throw new HttpError(error.response.status); // throws: 'cannot access status of undefined' 17}
FAQ
Why is there an isError
attribute? And why always check it?
wrapException
returns a control variable called isError
that is useful to identify if the returned values from the executed function is an Error or a Success (data
).
Ideally, this wouldn't be necessary and the check would be as follows:
1const result = fn(); 2if (result.error) { 3 // result.error is unknown so it has to be handled 4 // result.data is undefined 5} else { 6 // result.error is undefined 7 // result.data is the type the function defined.. T 8}
This is not possible because the default returned type for the error
is unknown
as the following types:
1type ErrorResponse = { error: unknown; data: undefined }; 2type SuccessResponse = { error: undefined; data: T }; 3 4type WrappedResponse = SuccessResponse | ErrorResponse;
In Typescript, unknown
is 'stronger' than any other type because anything can be assigned to it. i.e. an union type of unknown | undefined
becomes unknown
.
Which means the union of the above mentioned types becomes something like:
1type WrappedResponse = { error: unknown; data: T | undefined };
Notice that the undefined
union is now gone. This results in the following behavior:
1const result = fn(); 2if (result.error) { 3 // result.error is unknown so it has to be handled 4 // result.data is T | undefined (which is not true! here the data would be undefined) 5} else { 6 // result.error is still unknown 7 // result.data is T | undefined (which is not true! here data would already be of type "T") 8}
isError
for the rescue!
It serves as a unique identifier in the union type so that Typescript can infer the resulting type properly:
1const result = fn(); 2if (result.isError) { 3 // result.error is unknown so it has to be handled 4 // result.data is unknown (serves the same purpose as "undefined") 5} else { 6 // result.error is undefined (now we can be SURE it is not an error) 7 // result.data is the type the function defined = T 8}
![Empty State](/_next/static/media/empty.e5fae2e5.png)
No vulnerabilities found.
![Empty State](/_next/static/media/empty.e5fae2e5.png)
No security vulnerabilities found.