From f19a5ec2b185ed85e76947e81d3ec1b00549510d Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L. de Mello" Date: Thu, 18 Jan 2024 17:22:05 -0300 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20=E2=9C=A8=20"try"=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.d.ts | 5 +-- src/index.js | 8 ++--- src/try.d.ts | 21 ++++++++++++ src/try.js | 70 ++++++++++++++++++++++++++++++++++++++++ test/placeholder.test.js | 7 ---- test/try.test.js | 35 ++++++++++++++++++++ 6 files changed, 130 insertions(+), 16 deletions(-) create mode 100644 src/try.d.ts create mode 100644 src/try.js delete mode 100644 test/placeholder.test.js create mode 100644 test/try.test.js diff --git a/src/index.d.ts b/src/index.d.ts index 40a0c5d..ddb1236 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1,6 +1,3 @@ -interface Test { - name: string, -} +export * from './try.d.ts'; -export default Test; diff --git a/src/index.js b/src/index.js index f6a705a..36ae513 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,3 @@ -/* - * Placeholder file - */ -// eslint-disable-next-line no-console -console.log('Hello, world!'); + +export * from './try.js'; + diff --git a/src/try.d.ts b/src/try.d.ts new file mode 100644 index 0000000..e15799e --- /dev/null +++ b/src/try.d.ts @@ -0,0 +1,21 @@ + +type WrappedResult = [Error, undefined] | [null, R]; + +function tryAsync< + F extends (...args: Parameters) => (ReturnType extends Promise + ? ReturnType + : Promise> + ), +>(func: F): (...args: Parameters) => +Promise>>>; + +function trySync< + F extends (...args: Parameters) => ReturnType, +>(func: F): (...args: Parameters) => WrappedResult>; + +export { + tryAsync as tryA, + tryAsync, + trySync as tryS, + trySync, +}; diff --git a/src/try.js b/src/try.js new file mode 100644 index 0000000..85525ad --- /dev/null +++ b/src/try.js @@ -0,0 +1,70 @@ +/* eslint-disable no-secrets/no-secrets */ + +/** + * @typedef {[Error, undefined] | [null, R]} WrappedResult + * @template R + */ + +/** + * @template {(...args: Parameters) => Promise>>} F + * @param {F} func - The function to be executed. + * @returns {(...args: Parameters) => Promise>>>} + */ +function tryAsync(func) { + /** + * @param {Parameters} args - The arguments of the function. + * @returns {Promise>>>} + */ + return async (...args) => { + try { + return [null, await func(...args)]; + } + catch (error) { + if (error instanceof Error) return [error, undefined]; + + const errObj = new Error(error?.toString + // eslint-disable-next-line @typescript-eslint/no-base-to-string + ? `Stringified error to: ${error.toString()}` + : 'Could not stringify error', + { cause: { value: error } }); + + return [errObj, undefined]; + } + }; +} + +/** + * @template {(...args: Parameters) => ReturnType} F + * @param {F} func - The function to be executed. + * @returns {(...args: Parameters) => WrappedResult>} + */ +function trySync(func) { + /** + * @param {Parameters} args - The arguments of the function. + * @returns {WrappedResult>} + */ + return (...args) => { + try { + return [null, func(...args)]; + } + catch (error) { + if (error instanceof Error) return [error, undefined]; + + const errObj = new Error(error?.toString + // eslint-disable-next-line @typescript-eslint/no-base-to-string + ? `Stringified error to: ${error.toString()}` + : 'Could not stringify error', + { cause: { value: error } }); + + return [errObj, undefined]; + } + }; +} + +export { + tryAsync as tryA, + tryAsync, + trySync as tryS, + trySync, +}; + diff --git a/test/placeholder.test.js b/test/placeholder.test.js deleted file mode 100644 index c094da5..0000000 --- a/test/placeholder.test.js +++ /dev/null @@ -1,7 +0,0 @@ -// eslint-disable-next-line n/no-unpublished-import -import { expect, test } from 'vitest'; - -test('placeholder', () => { - expect(1).toBe(1); -}); - diff --git a/test/try.test.js b/test/try.test.js new file mode 100644 index 0000000..c3fbb03 --- /dev/null +++ b/test/try.test.js @@ -0,0 +1,35 @@ +/* eslint-disable import/no-relative-parent-imports */ +// eslint-disable-next-line n/no-unpublished-import +import { describe, it } from 'vitest'; + +import { tryA, tryS } from '../src/index.js'; + +describe.concurrent('Return values', () => { + it('JSON parsing [Sync, Success]', ({ expect }) => { + const [error, json] = tryS(JSON.parse)('{ "hello": "world" }'); + + expect(error).toBe(null); + expect(json).toEqual({ hello: 'world' }); + }); + it('JSON parsing [Sync, Error]', ({ expect }) => { + const [error, json] = tryS(JSON.parse)('{ "hello: "world" }'); + + expect(error?.name).toEqual('SyntaxError'); + expect(error).toBeInstanceOf(Error); + expect(json).toBe(undefined); + }); + it('Fetch function [Async, Success]', async ({ expect }) => { + const [error, res] = await tryA(fetch)('https://example.com'); + + expect(error).toBe(null); + expect(res?.status).toBe(200); + }); + it('Fetch function [Async, Error]', async ({ expect }) => { + const [error, res] = await tryA(fetch)('htps://example.com'); + + expect(error?.name).toEqual('TypeError'); + expect(error).toBeInstanceOf(Error); + expect(res).toBe(undefined); + }); +}); + From 3444a1c5daa3c0ba708aeb6814859d74a472c622 Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L. de Mello" Date: Thu, 18 Jan 2024 18:08:48 -0300 Subject: [PATCH 2/8] =?UTF-8?q?docs:=20=F0=9F=93=9A=EF=B8=8F=20add=20docum?= =?UTF-8?q?entation=20using=20jsdocs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/try.d.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ src/try.js | 48 +++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/src/try.d.ts b/src/try.d.ts index e15799e..bd2812f 100644 --- a/src/try.d.ts +++ b/src/try.d.ts @@ -1,6 +1,27 @@ type WrappedResult = [Error, undefined] | [null, R]; +/** + * Function-sugar/Syntax-sugar for handling functions that can throw errors. Wrapping then + * into a try-catch block / "curried function" that returns a "tuple as array" of error and + * value, which can be used for handling the error using a Go-like fashion. **This function + * is for asynchronous operations,** for synchronous ones, see {@link trySync}. + * + * **If there's a error, the result is undefined**. + * If there's not a error, error will be null and the result will be defined. + * + * @param func + * - The function to be executed. + * @returns + * - The function to be immediately called with the wrapped function's arguments. + * @example + * const [error, res] = await tryAsync(fetch)("https://example.com"); + * if (error !== null) { + * // error handling... + * console.log(error); + * } + * // continue the logic... + */ function tryAsync< F extends (...args: Parameters) => (ReturnType extends Promise ? ReturnType @@ -9,6 +30,27 @@ function tryAsync< >(func: F): (...args: Parameters) => Promise>>>; +/** + * Function-sugar/Syntax-sugar for handling functions that can throw errors. Wrapping then + * into a try-catch block / "curried function" that returns a "tuple as array" of error and + * value, which can be used for handling the error using a Go-like fashion. **This function + * is for synchronous operations,** for asynchronous ones, see {@link tryAsync}. + * + * **If there's a error, the result is undefined**. + * If there's not a error, error will be null and the result will be defined. + * + * @param func + * - The function to be executed. + * @returns + * - The function to be immediately called with the wrapped function's arguments. + * @example + * const [error, json] = trySync(JSON.parse)('{ "hello": "world" }'); + * if (error !== null) { + * // error handling... + * console.log(error); + * } + * // continue the logic... + */ function trySync< F extends (...args: Parameters) => ReturnType, >(func: F): (...args: Parameters) => WrappedResult>; diff --git a/src/try.js b/src/try.js index 85525ad..fdb146d 100644 --- a/src/try.js +++ b/src/try.js @@ -6,14 +6,35 @@ */ /** + * Function-sugar/Syntax-sugar for handling functions that can throw errors. Wrapping then + * into a try-catch block / "curried function" that returns a "tuple as array" of error and + * value, which can be used for handling the error using a Go-like fashion. **This function + * is for asynchronous operations,** for synchronous ones, see {@link trySync}. + * + * **If there's a error, the result is undefined**. + * If there's not a error, error will be null and the result will be defined. + * * @template {(...args: Parameters) => Promise>>} F - * @param {F} func - The function to be executed. + * @param {F} func + * - The function to be executed. * @returns {(...args: Parameters) => Promise>>>} + * - The function to be immediately called with the wrapped function's arguments. + * @example + * const [error, res] = await tryAsync(fetch)("https://example.com"); + * if (error !== null) { + * // error handling... + * console.log(error); + * } + * // continue the logic... */ function tryAsync(func) { /** + * The returned function from {@link tryAsync}. + * * @param {Parameters} args - The arguments of the function. + * - The arguments of the wrapped function. * @returns {Promise>>>} + * - The final tuple containing the Error object (if one occured) and the resulting value. */ return async (...args) => { try { @@ -34,14 +55,35 @@ function tryAsync(func) { } /** + * Function-sugar/Syntax-sugar for handling functions that can throw errors. Wrapping then + * into a try-catch block / "curried function" that returns a "tuple as array" of error and + * value, which can be used for handling the error using a Go-like fashion. **This function + * is for synchronous operations,** for asynchronous ones, see {@link tryAsync}. + * + * **If there's a error, the result is undefined**. + * If there's not a error, error will be null and the result will be defined. + * * @template {(...args: Parameters) => ReturnType} F - * @param {F} func - The function to be executed. + * @param {F} func + * - The function to be executed. * @returns {(...args: Parameters) => WrappedResult>} + * - The function to be immediately called with the wrapped function's arguments. + * @example + * const [error, json] = trySync(JSON.parse)('{ "hello": "world" }'); + * if (error !== null) { + * // error handling... + * console.log(error); + * } + * // continue the logic... */ function trySync(func) { /** - * @param {Parameters} args - The arguments of the function. + * The returned function from {@link trySync}. + * + * @param {Parameters} args + * - The arguments of the wrapped function. * @returns {WrappedResult>} + * - The final tuple containing the Error object (if one occured) and the resulting value. */ return (...args) => { try { From 02490a2502f9b7eb1ca1a3ccf52b171e82d66b6f Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L. de Mello" Date: Thu, 18 Jan 2024 18:29:51 -0300 Subject: [PATCH 3/8] =?UTF-8?q?refactor:=20=E2=99=BB=EF=B8=8F=20improve=20?= =?UTF-8?q?and=20export=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/try.d.ts | 37 ++++++++++++++++++++++++++++++++++--- src/try.js | 25 +++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/src/try.d.ts b/src/try.d.ts index bd2812f..284a626 100644 --- a/src/try.d.ts +++ b/src/try.d.ts @@ -1,6 +1,24 @@ +/** + * The WrappedResult type returned by the wrapped function in {@link trySync} + * and {@link tryAsync}. + * + * **If a error occurred, the result is undefined**. + * If there's not a error, error will be null and the result will be defined. + */ type WrappedResult = [Error, undefined] | [null, R]; +/** + * The returned function from {@link tryAsync}. + * + * @param args - The arguments of the function. + * - The arguments of the wrapped function. + * @returns + * - The final tuple containing the Error object (if one occured) and the resulting value. + */ +type WrappedAsyncFunction = (...args: Parameters) => +Promise>>>; + /** * Function-sugar/Syntax-sugar for handling functions that can throw errors. Wrapping then * into a try-catch block / "curried function" that returns a "tuple as array" of error and @@ -27,8 +45,18 @@ function tryAsync< ? ReturnType : Promise> ), ->(func: F): (...args: Parameters) => -Promise>>>; +>(func: F): WrappedAsyncFunction; + +/** + * The returned function from {@link trySync}. + * + * @param args + * - The arguments of the wrapped function. + * @returns + * - The final tuple containing the Error object (if one occured) and the resulting value. + */ +type WrappedFunction = (...args: Parameters) => +WrappedResult>; /** * Function-sugar/Syntax-sugar for handling functions that can throw errors. Wrapping then @@ -53,9 +81,12 @@ Promise>>>; */ function trySync< F extends (...args: Parameters) => ReturnType, ->(func: F): (...args: Parameters) => WrappedResult>; +>(func: F): WrappedFunction; export { + type WrappedAsyncFunction, + type WrappedFunction, + type WrappedResult, tryAsync as tryA, tryAsync, trySync as tryS, diff --git a/src/try.js b/src/try.js index fdb146d..ae117fc 100644 --- a/src/try.js +++ b/src/try.js @@ -1,10 +1,24 @@ /* eslint-disable no-secrets/no-secrets */ /** + * The WrappedResult type returned by the wrapped function in {@link trySync} + * and {@link tryAsync}. + * + * **If a error occurred, the result is undefined**. + * If there's not a error, error will be null and the result will be defined. + * * @typedef {[Error, undefined] | [null, R]} WrappedResult * @template R */ +/** + * The returned function from {@link tryAsync}. + * + * @typedef {(...args: Parameters) => Promise>>>} + * WrappedAsyncFunction + * @template {(...args: Parameters) => ReturnType} F + */ + /** * Function-sugar/Syntax-sugar for handling functions that can throw errors. Wrapping then * into a try-catch block / "curried function" that returns a "tuple as array" of error and @@ -17,7 +31,7 @@ * @template {(...args: Parameters) => Promise>>} F * @param {F} func * - The function to be executed. - * @returns {(...args: Parameters) => Promise>>>} + * @returns {WrappedAsyncFunction} * - The function to be immediately called with the wrapped function's arguments. * @example * const [error, res] = await tryAsync(fetch)("https://example.com"); @@ -54,6 +68,13 @@ function tryAsync(func) { }; } +/** + * The returned function from {@link trySync}. + * + * @typedef {(...args: Parameters) => WrappedResult>} WrappedFunction + * @template {(...args: Parameters) => ReturnType} F + */ + /** * Function-sugar/Syntax-sugar for handling functions that can throw errors. Wrapping then * into a try-catch block / "curried function" that returns a "tuple as array" of error and @@ -66,7 +87,7 @@ function tryAsync(func) { * @template {(...args: Parameters) => ReturnType} F * @param {F} func * - The function to be executed. - * @returns {(...args: Parameters) => WrappedResult>} + * @returns {WrappedFunction} * - The function to be immediately called with the wrapped function's arguments. * @example * const [error, json] = trySync(JSON.parse)('{ "hello": "world" }'); From ae87a2da7bb40fbb741bd35ec08ed3d96e798d94 Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L. de Mello" Date: Thu, 18 Jan 2024 18:50:50 -0300 Subject: [PATCH 4/8] =?UTF-8?q?feat!:=20=F0=9F=92=A5=20=E2=9C=A8=20invert?= =?UTF-8?q?=20order=20of=20array?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/try.d.ts | 2 +- src/try.js | 14 +++++++------- test/try.test.js | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/try.d.ts b/src/try.d.ts index 284a626..4377547 100644 --- a/src/try.d.ts +++ b/src/try.d.ts @@ -6,7 +6,7 @@ * **If a error occurred, the result is undefined**. * If there's not a error, error will be null and the result will be defined. */ -type WrappedResult = [Error, undefined] | [null, R]; +type WrappedResult = [R, null] | [undefined, Error]; /** * The returned function from {@link tryAsync}. diff --git a/src/try.js b/src/try.js index ae117fc..c78ee01 100644 --- a/src/try.js +++ b/src/try.js @@ -7,7 +7,7 @@ * **If a error occurred, the result is undefined**. * If there's not a error, error will be null and the result will be defined. * - * @typedef {[Error, undefined] | [null, R]} WrappedResult + * @typedef {[R, null] | [undefined, Error]} WrappedResult * @template R */ @@ -52,10 +52,10 @@ function tryAsync(func) { */ return async (...args) => { try { - return [null, await func(...args)]; + return [await func(...args), null]; } catch (error) { - if (error instanceof Error) return [error, undefined]; + if (error instanceof Error) return [undefined, error]; const errObj = new Error(error?.toString // eslint-disable-next-line @typescript-eslint/no-base-to-string @@ -63,7 +63,7 @@ function tryAsync(func) { : 'Could not stringify error', { cause: { value: error } }); - return [errObj, undefined]; + return [undefined, errObj]; } }; } @@ -108,10 +108,10 @@ function trySync(func) { */ return (...args) => { try { - return [null, func(...args)]; + return [func(...args), null]; } catch (error) { - if (error instanceof Error) return [error, undefined]; + if (error instanceof Error) return [undefined, error]; const errObj = new Error(error?.toString // eslint-disable-next-line @typescript-eslint/no-base-to-string @@ -119,7 +119,7 @@ function trySync(func) { : 'Could not stringify error', { cause: { value: error } }); - return [errObj, undefined]; + return [undefined, errObj]; } }; } diff --git a/test/try.test.js b/test/try.test.js index c3fbb03..9f931ae 100644 --- a/test/try.test.js +++ b/test/try.test.js @@ -6,26 +6,26 @@ import { tryA, tryS } from '../src/index.js'; describe.concurrent('Return values', () => { it('JSON parsing [Sync, Success]', ({ expect }) => { - const [error, json] = tryS(JSON.parse)('{ "hello": "world" }'); + const [json, error] = tryS(JSON.parse)('{ "hello": "world" }'); expect(error).toBe(null); expect(json).toEqual({ hello: 'world' }); }); it('JSON parsing [Sync, Error]', ({ expect }) => { - const [error, json] = tryS(JSON.parse)('{ "hello: "world" }'); + const [json, error] = tryS(JSON.parse)('{ "hello: "world" }'); expect(error?.name).toEqual('SyntaxError'); expect(error).toBeInstanceOf(Error); expect(json).toBe(undefined); }); it('Fetch function [Async, Success]', async ({ expect }) => { - const [error, res] = await tryA(fetch)('https://example.com'); + const [res, error] = await tryA(fetch)('https://example.com'); expect(error).toBe(null); expect(res?.status).toBe(200); }); it('Fetch function [Async, Error]', async ({ expect }) => { - const [error, res] = await tryA(fetch)('htps://example.com'); + const [res, error] = await tryA(fetch)('htps://example.com'); expect(error?.name).toEqual('TypeError'); expect(error).toBeInstanceOf(Error); From ecfbfc7ea438ce009c02198bf7faddcd703682c5 Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L. de Mello" Date: Thu, 18 Jan 2024 18:53:41 -0300 Subject: [PATCH 5/8] =?UTF-8?q?chore:=20=F0=9F=94=A7=20add=20changeset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/cool-days-grow.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cool-days-grow.md diff --git a/.changeset/cool-days-grow.md b/.changeset/cool-days-grow.md new file mode 100644 index 0000000..244394d --- /dev/null +++ b/.changeset/cool-days-grow.md @@ -0,0 +1,5 @@ +--- +"lilbetter.js": minor +--- + +Created the tryAsync (tryA) and trySync (tryS) functions, said can be used for wrapping and calling functions that can throw error. Said error can then be handled using in a Go-like fashion. From a4f857e71ea111ab305fa2b3821d95ef73a207e6 Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L. de Mello" Date: Thu, 18 Jan 2024 19:05:57 -0300 Subject: [PATCH 6/8] =?UTF-8?q?chore:=20=F0=9F=94=A7=20package.json=20expo?= =?UTF-8?q?rts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 68e56bf..ccc5ddf 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,16 @@ "url": "https://github.com/LoredDev/lilbetter.js" }, "exports": { - "import": "./src/index.js", - "default": "./src/index.js", - "types": "./src/index.d.ts" + ".": { + "import": "./src/index.js", + "default": "./src/index.js", + "types": "./src/index.d.ts" + }, + "./try": { + "import": "./src/try.js", + "default": "./src/try.js", + "types": "./src/try.d.ts" + } }, "files": [ "./src/**/*.js", From 4c3e60cf853ade87e43166c6a46801c2d5a43960 Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L. de Mello" Date: Thu, 18 Jan 2024 19:11:04 -0300 Subject: [PATCH 7/8] =?UTF-8?q?chore:=20=F0=9F=94=A7=20fix=20node=20versio?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ccc5ddf..5b69731 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ }, "license": "MIT", "engines": { - "node": ">=20", + "node": ">=18", "pnpm": ">=8" }, "publishConfig": { From be5ea204ce58354f85077b772ca7281a527f75f5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 18 Jan 2024 22:11:58 +0000 Subject: [PATCH 8/8] =?UTF-8?q?ci:=20=F0=9F=91=B7=F0=9F=A6=8B=20version=20?= =?UTF-8?q?packages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/cool-days-grow.md | 5 ----- CHANGELOG.md | 6 ++++++ package.json | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) delete mode 100644 .changeset/cool-days-grow.md diff --git a/.changeset/cool-days-grow.md b/.changeset/cool-days-grow.md deleted file mode 100644 index 244394d..0000000 --- a/.changeset/cool-days-grow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"lilbetter.js": minor ---- - -Created the tryAsync (tryA) and trySync (tryS) functions, said can be used for wrapping and calling functions that can throw error. Said error can then be handled using in a Go-like fashion. diff --git a/CHANGELOG.md b/CHANGELOG.md index a296a92..7ec8c2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # lilbetter.js +## 0.1.0 + +### Minor Changes + +- ecfbfc7: Created the tryAsync (tryA) and trySync (tryS) functions, said can be used for wrapping and calling functions that can throw error. Said error can then be handled using in a Go-like fashion. + ## 0.0.3 ### Patch Changes diff --git a/package.json b/package.json index 5b69731..39d212d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lilbetter.js", - "version": "0.0.3", + "version": "0.1.0", "description": "", "main": "./src/index.js", "browser": "./src/index.js",