From 7b586e85ab090b3656c297876f97ce7d4c4644e3 Mon Sep 17 00:00:00 2001 From: Inaiat Moraes Date: Sat, 13 Apr 2024 22:46:49 -0300 Subject: [PATCH 1/3] chore: doc improvements --- BUILD_SHA | 2 +- README.md | 60 ++++++++++++- package.json | 2 +- pnpm-lock.yaml | 210 +++++++++++++++++++++---------------------- src/result-async.ts | 87 ++++++++++-------- src/result.ts | 7 +- tests/result-test.ts | 27 ++++++ 7 files changed, 239 insertions(+), 156 deletions(-) diff --git a/BUILD_SHA b/BUILD_SHA index a45a73e..93b0e5c 100644 --- a/BUILD_SHA +++ b/BUILD_SHA @@ -1 +1 @@ -3eebd1d3875783ff1f972f8bfac9d810a18d479e +d3c9909935d9336f9c9848a07af4255c795a0915 diff --git a/README.md b/README.md index 85f9111..3e96bc9 100644 --- a/README.md +++ b/README.md @@ -49,18 +49,20 @@ For asynchronous tasks, `resultar` offers a `ResultAsync` class which wraps a `P - [`errAsync`](#errasync) - [`ResultAsync.fromPromise` (static class method)](#resultasyncfrompromise-static-class-method) - [`ResultAsync.fromSafePromise` (static class method)](#resultasyncfromsafepromise-static-class-method) + - [`ResultAsync.fromThrowable` (static class method)](#resultasyncfromthrowable-static-class-method) - [`ResultAsync.map` (method)](#resultasyncmap-method) - [`ResultAsync.mapErr` (method)](#resultasyncmaperr-method) - [`ResultAsync.unwrapOr` (method)](#resultasyncunwrapor-method) - [`ResultAsync.andThen` (method)](#resultasyncandthen-method) - [`ResultAsync.orElse` (method)](#resultasyncorelse-method) - - [`Result.tap` (method)](#resultasynctap-method) + - [`ResultAsync.tap` (method)](#resultasynctap-method) - [`ResultAsync.match` (method)](#resultasyncmatch-method) - [`ResultAsync.combine` (static class method)](#resultasynccombine-static-class-method) - [`ResultAsync.combineWithAllErrors` (static class method)](#resultasynccombinewithallerrors-static-class-method) - [`ResultAsync.safeUnwrap()`](#resultasyncsafeunwrap) + [Utilities](#utilities) - [`fromThrowable`](#fromthrowable) + - [`fromThrowableAsync`](#fromthrowableasync) - [`fromPromise`](#frompromise) - [`fromSafePromise`](#fromsafepromise) - [`safeTry`](#safetry) @@ -712,6 +714,44 @@ myResult.isErr() // true --- +#### `ResultAsync.fromThrowable` (static class method) + +Similar to [Result.fromThrowable](#resultfromthrowable-static-class-method), but for functions that return a `Promise`. + +**Example**: + +```typescript +import { ResultAsync } from 'neverthrow' +import { insertIntoDb } from 'imaginary-database' +// insertIntoDb(user: User): Promise + +const insertUser = ResultAsync.fromThrowable(insertIntoDb, () => new Error('Database error')) +// `res` has a type of (user: User) => ResultAsync +``` + +Note that this can be safer than using [ResultAsync.fromPromise](#resultasyncfrompromise-static-class-method) with +the result of a function call, because not all functions that return a `Promise` are `async`, and thus they can throw +errors synchronously rather than returning a rejected `Promise`. For example: + +```typescript +import { readFile } from 'node:fs/promises' +import { fromThrowableAsync } from 'resultar' + +const safeFileReader = fromThrowableAsync( + async (file: string) => readFile(file), + e => new Error('Oops: '.concat(String(e))), +) +const value = await safeFileReader('foo.txt') + +if (value.isOk()) { + console.log(value.value.toString()) +} +``` + +[⬆️ Back to top](#toc) + +--- + #### `ResultAsync.fromPromise` (static class method) Transforms a `PromiseLike` (that may throw) into a `ResultAsync`. @@ -981,7 +1021,7 @@ This method is useful for performing actions that do not modify the `Result` its **Signature:** ```typescript class ResultAsync { - tap(f: (t: T) => void): Result {..} + tap(f: (t: T) => void | Promise): ResultAsync { } ``` @@ -995,9 +1035,9 @@ const mapped = okVal.tap((value) => { // mapped.value === 'foo' ``` [⬆️ Back to top](#toc) ---- --- + #### `Result.finally` (method) Executes a cleanup function wether the is ok or error. This method is usefull to cleanup resources. **Signature:** @@ -1021,6 +1061,7 @@ if (resul.isOk()) { ``` [⬆️ Back to top](#toc) + --- #### `ResultAsync.orElse` (method) @@ -1055,9 +1096,9 @@ class ResultAsync { **Example:** See [`Result.tap` (method)](#resulttap-method) -``` [⬆️ Back to top](#toc) + --- #### `ResultAsync.match` (method) @@ -1206,6 +1247,17 @@ Please find documentation at [Result.fromThrowable](#resultfromthrowable-static- [⬆️ Back to top](#toc) +--- + +#### `fromThrowableAsync` + +Top level export of `ResultAsync.fromThrowable`. +Please find documentation at [ResultAsync.fromThrowable](#resultasyncfromthrowable-static-class-method) + +[⬆️ Back to top](#toc) + +--- + #### `fromPromise` Top level export of `ResultAsync.fromPromise`. diff --git a/package.json b/package.json index a6cf1a6..078f6e0 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "typedoc": "^0.25.13", "typedoc-github-wiki-theme": "2.0.0-next.8", "typedoc-plugin-markdown": "4.0.0-next.55", - "typescript": "5.4.4", + "typescript": "5.4.5", "xo": "0.58.0" }, "tsup": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5db82c5..6f3f555 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,13 +25,13 @@ devDependencies: version: 3.20.2 tsup: specifier: ^8.0.2 - version: 8.0.2(typescript@5.4.4) + version: 8.0.2(typescript@5.4.5) tsx: specifier: ^4.7.2 version: 4.7.2 typedoc: specifier: ^0.25.13 - version: 0.25.13(typescript@5.4.4) + version: 0.25.13(typescript@5.4.5) typedoc-github-wiki-theme: specifier: 2.0.0-next.8 version: 2.0.0-next.8(typedoc-plugin-markdown@4.0.0-next.55) @@ -39,8 +39,8 @@ devDependencies: specifier: 4.0.0-next.55 version: 4.0.0-next.55(typedoc@0.25.13) typescript: - specifier: 5.4.4 - version: 5.4.4 + specifier: 5.4.5 + version: 5.4.5 xo: specifier: 0.58.0 version: 0.58.0(webpack@5.91.0) @@ -438,120 +438,120 @@ packages: engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} dev: true - /@rollup/rollup-android-arm-eabi@4.14.1: - resolution: {integrity: sha512-fH8/o8nSUek8ceQnT7K4EQbSiV7jgkHq81m9lWZFIXjJ7lJzpWXbQFpT/Zh6OZYnpFykvzC3fbEvEAFZu03dPA==} + /@rollup/rollup-android-arm-eabi@4.14.2: + resolution: {integrity: sha512-ahxSgCkAEk+P/AVO0vYr7DxOD3CwAQrT0Go9BJyGQ9Ef0QxVOfjDZMiF4Y2s3mLyPrjonchIMH/tbWHucJMykQ==} cpu: [arm] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-android-arm64@4.14.1: - resolution: {integrity: sha512-Y/9OHLjzkunF+KGEoJr3heiD5X9OLa8sbT1lm0NYeKyaM3oMhhQFvPB0bNZYJwlq93j8Z6wSxh9+cyKQaxS7PQ==} + /@rollup/rollup-android-arm64@4.14.2: + resolution: {integrity: sha512-lAarIdxZWbFSHFSDao9+I/F5jDaKyCqAPMq5HqnfpBw8dKDiCaaqM0lq5h1pQTLeIqueeay4PieGR5jGZMWprw==} cpu: [arm64] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-arm64@4.14.1: - resolution: {integrity: sha512-+kecg3FY84WadgcuSVm6llrABOdQAEbNdnpi5X3UwWiFVhZIZvKgGrF7kmLguvxHNQy+UuRV66cLVl3S+Rkt+Q==} + /@rollup/rollup-darwin-arm64@4.14.2: + resolution: {integrity: sha512-SWsr8zEUk82KSqquIMgZEg2GE5mCSfr9sE/thDROkX6pb3QQWPp8Vw8zOq2GyxZ2t0XoSIUlvHDkrf5Gmf7x3Q==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-x64@4.14.1: - resolution: {integrity: sha512-2pYRzEjVqq2TB/UNv47BV/8vQiXkFGVmPFwJb+1E0IFFZbIX8/jo1olxqqMbo6xCXf8kabANhp5bzCij2tFLUA==} + /@rollup/rollup-darwin-x64@4.14.2: + resolution: {integrity: sha512-o/HAIrQq0jIxJAhgtIvV5FWviYK4WB0WwV91SLUnsliw1lSAoLsmgEEgRWzDguAFeUEUUoIWXiJrPqU7vGiVkA==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.14.1: - resolution: {integrity: sha512-mS6wQ6Do6/wmrF9aTFVpIJ3/IDXhg1EZcQFYHZLHqw6AzMBjTHWnCG35HxSqUNphh0EHqSM6wRTT8HsL1C0x5g==} + /@rollup/rollup-linux-arm-gnueabihf@4.14.2: + resolution: {integrity: sha512-nwlJ65UY9eGq91cBi6VyDfArUJSKOYt5dJQBq8xyLhvS23qO+4Nr/RreibFHjP6t+5ap2ohZrUJcHv5zk5ju/g==} cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-gnu@4.14.1: - resolution: {integrity: sha512-p9rGKYkHdFMzhckOTFubfxgyIO1vw//7IIjBBRVzyZebWlzRLeNhqxuSaZ7kCEKVkm/kuC9fVRW9HkC/zNRG2w==} + /@rollup/rollup-linux-arm64-gnu@4.14.2: + resolution: {integrity: sha512-Pg5TxxO2IVlMj79+c/9G0LREC9SY3HM+pfAwX7zj5/cAuwrbfj2Wv9JbMHIdPCfQpYsI4g9mE+2Bw/3aeSs2rQ==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-musl@4.14.1: - resolution: {integrity: sha512-nDY6Yz5xS/Y4M2i9JLQd3Rofh5OR8Bn8qe3Mv/qCVpHFlwtZSBYSPaU4mrGazWkXrdQ98GB//H0BirGR/SKFSw==} + /@rollup/rollup-linux-arm64-musl@4.14.2: + resolution: {integrity: sha512-cAOTjGNm84gc6tS02D1EXtG7tDRsVSDTBVXOLbj31DkwfZwgTPYZ6aafSU7rD/4R2a34JOwlF9fQayuTSkoclA==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-powerpc64le-gnu@4.14.1: - resolution: {integrity: sha512-im7HE4VBL+aDswvcmfx88Mp1soqL9OBsdDBU8NqDEYtkri0qV0THhQsvZtZeNNlLeCUQ16PZyv7cqutjDF35qw==} - cpu: [ppc64le] + /@rollup/rollup-linux-powerpc64le-gnu@4.14.2: + resolution: {integrity: sha512-4RyT6v1kXb7C0fn6zV33rvaX05P0zHoNzaXI/5oFHklfKm602j+N4mn2YvoezQViRLPnxP8M1NaY4s/5kXO5cw==} + cpu: [ppc64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-riscv64-gnu@4.14.1: - resolution: {integrity: sha512-RWdiHuAxWmzPJgaHJdpvUUlDz8sdQz4P2uv367T2JocdDa98iRw2UjIJ4QxSyt077mXZT2X6pKfT2iYtVEvOFw==} + /@rollup/rollup-linux-riscv64-gnu@4.14.2: + resolution: {integrity: sha512-KNUH6jC/vRGAKSorySTyc/yRYlCwN/5pnMjXylfBniwtJx5O7X17KG/0efj8XM3TZU7raYRXJFFReOzNmL1n1w==} cpu: [riscv64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-s390x-gnu@4.14.1: - resolution: {integrity: sha512-VMgaGQ5zRX6ZqV/fas65/sUGc9cPmsntq2FiGmayW9KMNfWVG/j0BAqImvU4KTeOOgYSf1F+k6at1UfNONuNjA==} + /@rollup/rollup-linux-s390x-gnu@4.14.2: + resolution: {integrity: sha512-xPV4y73IBEXToNPa3h5lbgXOi/v0NcvKxU0xejiFw6DtIYQqOTMhZ2DN18/HrrP0PmiL3rGtRG9gz1QE8vFKXQ==} cpu: [s390x] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-gnu@4.14.1: - resolution: {integrity: sha512-9Q7DGjZN+hTdJomaQ3Iub4m6VPu1r94bmK2z3UeWP3dGUecRC54tmVu9vKHTm1bOt3ASoYtEz6JSRLFzrysKlA==} + /@rollup/rollup-linux-x64-gnu@4.14.2: + resolution: {integrity: sha512-QBhtr07iFGmF9egrPOWyO5wciwgtzKkYPNLVCFZTmr4TWmY0oY2Dm/bmhHjKRwZoGiaKdNcKhFtUMBKvlchH+Q==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-musl@4.14.1: - resolution: {integrity: sha512-JNEG/Ti55413SsreTguSx0LOVKX902OfXIKVg+TCXO6Gjans/k9O6ww9q3oLGjNDaTLxM+IHFMeXy/0RXL5R/g==} + /@rollup/rollup-linux-x64-musl@4.14.2: + resolution: {integrity: sha512-8zfsQRQGH23O6qazZSFY5jP5gt4cFvRuKTpuBsC1ZnSWxV8ZKQpPqOZIUtdfMOugCcBvFGRa1pDC/tkf19EgBw==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-arm64-msvc@4.14.1: - resolution: {integrity: sha512-ryS22I9y0mumlLNwDFYZRDFLwWh3aKaC72CWjFcFvxK0U6v/mOkM5Up1bTbCRAhv3kEIwW2ajROegCIQViUCeA==} + /@rollup/rollup-win32-arm64-msvc@4.14.2: + resolution: {integrity: sha512-H4s8UjgkPnlChl6JF5empNvFHp77Jx+Wfy2EtmYPe9G22XV+PMuCinZVHurNe8ggtwoaohxARJZbaH/3xjB/FA==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-ia32-msvc@4.14.1: - resolution: {integrity: sha512-TdloItiGk+T0mTxKx7Hp279xy30LspMso+GzQvV2maYePMAWdmrzqSNZhUpPj3CGw12aGj57I026PgLCTu8CGg==} + /@rollup/rollup-win32-ia32-msvc@4.14.2: + resolution: {integrity: sha512-djqpAjm/i8erWYF0K6UY4kRO3X5+T4TypIqw60Q8MTqSBaQNpNXDhxdjpZ3ikgb+wn99svA7jxcXpiyg9MUsdw==} cpu: [ia32] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-x64-msvc@4.14.1: - resolution: {integrity: sha512-wQGI+LY/Py20zdUPq+XCem7JcPOyzIJBm3dli+56DJsQOHbnXZFEwgmnC6el1TPAfC8lBT3m+z69RmLykNUbew==} + /@rollup/rollup-win32-x64-msvc@4.14.2: + resolution: {integrity: sha512-teAqzLT0yTYZa8ZP7zhFKEx4cotS8Tkk5XiqNMJhD4CpaWB1BHARE4Qy+RzwnXvSAYv+Q3jAqCVBS+PS+Yee8Q==} cpu: [x64] os: [win32] requiresBuild: true @@ -566,12 +566,12 @@ packages: /@types/eslint-scope@3.7.7: resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} dependencies: - '@types/eslint': 8.56.8 + '@types/eslint': 8.56.9 '@types/estree': 1.0.5 dev: true - /@types/eslint@8.56.8: - resolution: {integrity: sha512-LdDdQVDzDXf3ijhhMnE27C5vc0QEknD8GiMR/Hi+fVbdZNfAfCy2j69m0LjUd2MAy0+kIgnOtd5ndTmDk/VWCA==} + /@types/eslint@8.56.9: + resolution: {integrity: sha512-W4W3KcqzjJ0sHg2vAq9vfml6OhsJ53TcUjUqfzzZf/EChUtwspszj/S0pzMxnfRcO55/iGq47dscXw71Fxc4Zg==} dependencies: '@types/estree': 1.0.5 '@types/json-schema': 7.0.15 @@ -603,7 +603,7 @@ packages: resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} dev: true - /@typescript-eslint/eslint-plugin@7.6.0(@typescript-eslint/parser@7.6.0)(eslint@8.57.0)(typescript@5.4.4): + /@typescript-eslint/eslint-plugin@7.6.0(@typescript-eslint/parser@7.6.0)(eslint@8.57.0)(typescript@5.4.5): resolution: {integrity: sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: @@ -615,10 +615,10 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 7.6.0(eslint@8.57.0)(typescript@5.4.4) + '@typescript-eslint/parser': 7.6.0(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/scope-manager': 7.6.0 - '@typescript-eslint/type-utils': 7.6.0(eslint@8.57.0)(typescript@5.4.4) - '@typescript-eslint/utils': 7.6.0(eslint@8.57.0)(typescript@5.4.4) + '@typescript-eslint/type-utils': 7.6.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/utils': 7.6.0(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/visitor-keys': 7.6.0 debug: 4.3.4 eslint: 8.57.0 @@ -626,13 +626,13 @@ packages: ignore: 5.3.1 natural-compare: 1.4.0 semver: 7.6.0 - ts-api-utils: 1.3.0(typescript@5.4.4) - typescript: 5.4.4 + ts-api-utils: 1.3.0(typescript@5.4.5) + typescript: 5.4.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser@7.6.0(eslint@8.57.0)(typescript@5.4.4): + /@typescript-eslint/parser@7.6.0(eslint@8.57.0)(typescript@5.4.5): resolution: {integrity: sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: @@ -644,11 +644,11 @@ packages: dependencies: '@typescript-eslint/scope-manager': 7.6.0 '@typescript-eslint/types': 7.6.0 - '@typescript-eslint/typescript-estree': 7.6.0(typescript@5.4.4) + '@typescript-eslint/typescript-estree': 7.6.0(typescript@5.4.5) '@typescript-eslint/visitor-keys': 7.6.0 debug: 4.3.4 eslint: 8.57.0 - typescript: 5.4.4 + typescript: 5.4.5 transitivePeerDependencies: - supports-color dev: true @@ -661,7 +661,7 @@ packages: '@typescript-eslint/visitor-keys': 7.6.0 dev: true - /@typescript-eslint/type-utils@7.6.0(eslint@8.57.0)(typescript@5.4.4): + /@typescript-eslint/type-utils@7.6.0(eslint@8.57.0)(typescript@5.4.5): resolution: {integrity: sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: @@ -671,12 +671,12 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 7.6.0(typescript@5.4.4) - '@typescript-eslint/utils': 7.6.0(eslint@8.57.0)(typescript@5.4.4) + '@typescript-eslint/typescript-estree': 7.6.0(typescript@5.4.5) + '@typescript-eslint/utils': 7.6.0(eslint@8.57.0)(typescript@5.4.5) debug: 4.3.4 eslint: 8.57.0 - ts-api-utils: 1.3.0(typescript@5.4.4) - typescript: 5.4.4 + ts-api-utils: 1.3.0(typescript@5.4.5) + typescript: 5.4.5 transitivePeerDependencies: - supports-color dev: true @@ -686,7 +686,7 @@ packages: engines: {node: ^18.18.0 || >=20.0.0} dev: true - /@typescript-eslint/typescript-estree@7.6.0(typescript@5.4.4): + /@typescript-eslint/typescript-estree@7.6.0(typescript@5.4.5): resolution: {integrity: sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: @@ -702,13 +702,13 @@ packages: is-glob: 4.0.3 minimatch: 9.0.4 semver: 7.6.0 - ts-api-utils: 1.3.0(typescript@5.4.4) - typescript: 5.4.4 + ts-api-utils: 1.3.0(typescript@5.4.5) + typescript: 5.4.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@7.6.0(eslint@8.57.0)(typescript@5.4.4): + /@typescript-eslint/utils@7.6.0(eslint@8.57.0)(typescript@5.4.5): resolution: {integrity: sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: @@ -719,7 +719,7 @@ packages: '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 7.6.0 '@typescript-eslint/types': 7.6.0 - '@typescript-eslint/typescript-estree': 7.6.0(typescript@5.4.4) + '@typescript-eslint/typescript-estree': 7.6.0(typescript@5.4.5) eslint: 8.57.0 semver: 7.6.0 transitivePeerDependencies: @@ -1074,8 +1074,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001608 - electron-to-chromium: 1.4.735 + caniuse-lite: 1.0.30001609 + electron-to-chromium: 1.4.736 node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.23.0) dev: true @@ -1126,8 +1126,8 @@ packages: engines: {node: '>=6'} dev: true - /caniuse-lite@1.0.30001608: - resolution: {integrity: sha512-cjUJTQkk9fQlJR2s4HMuPMvTiRggl0rAVMtthQuyOlDWuqHXqN8azLq+pi8B2TjwKJ32diHjUqRIKeFX4z1FoA==} + /caniuse-lite@1.0.30001609: + resolution: {integrity: sha512-JFPQs34lHKx1B5t1EpQpWH4c+29zIyn/haGsbpfq3suuV9v56enjFt23zqijxGTMwy1p/4H2tjnQMY+p1WoAyA==} dev: true /chalk@2.4.2: @@ -1232,7 +1232,7 @@ packages: browserslist: 4.23.0 dev: true - /cosmiconfig@8.3.6(typescript@5.4.4): + /cosmiconfig@8.3.6(typescript@5.4.5): resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} engines: {node: '>=14'} peerDependencies: @@ -1245,7 +1245,7 @@ packages: js-yaml: 4.1.0 parse-json: 5.2.0 path-type: 4.0.0 - typescript: 5.4.4 + typescript: 5.4.5 dev: true /cross-spawn@7.0.3: @@ -1364,8 +1364,8 @@ packages: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: true - /electron-to-chromium@1.4.735: - resolution: {integrity: sha512-pkYpvwg8VyOTQAeBqZ7jsmpCjko1Qc6We1ZtZCjRyYbT5v4AIUKDy5cQTRotQlSSZmMr8jqpEt6JtOj5k7lR7A==} + /electron-to-chromium@1.4.736: + resolution: {integrity: sha512-Rer6wc3ynLelKNM4lOCg7/zPQj8tPOCB2hzD32PX9wd3hgRRi9MxEbmkFCokzcEhRVMiOVLjnL9ig9cefJ+6+Q==} dev: true /emoji-regex@10.3.0: @@ -1579,7 +1579,7 @@ packages: eslint: 8.57.0 dev: true - /eslint-config-xo-typescript@4.0.0(@typescript-eslint/eslint-plugin@7.6.0)(@typescript-eslint/parser@7.6.0)(eslint@8.57.0)(typescript@5.4.4): + /eslint-config-xo-typescript@4.0.0(@typescript-eslint/eslint-plugin@7.6.0)(@typescript-eslint/parser@7.6.0)(eslint@8.57.0)(typescript@5.4.5): resolution: {integrity: sha512-pmSWzVpvzEjZHG7S/rN34cFXAoe6YbvWFBQSitEXD5CcT2SULfykYl8hcYXss37r5N3SmJYAiO6VlcfkPiDRxg==} engines: {node: '>=18'} peerDependencies: @@ -1588,10 +1588,10 @@ packages: eslint: '>=8.56.0' typescript: '>=5.0.0' dependencies: - '@typescript-eslint/eslint-plugin': 7.6.0(@typescript-eslint/parser@7.6.0)(eslint@8.57.0)(typescript@5.4.4) - '@typescript-eslint/parser': 7.6.0(eslint@8.57.0)(typescript@5.4.4) + '@typescript-eslint/eslint-plugin': 7.6.0(@typescript-eslint/parser@7.6.0)(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/parser': 7.6.0(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 - typescript: 5.4.4 + typescript: 5.4.5 dev: true /eslint-config-xo@0.44.0(eslint@8.57.0): @@ -1608,7 +1608,7 @@ packages: resolution: {integrity: sha512-znAUcXmBthdIUmlnRkPSxz3zSJHFUhfHF/nJPcCMVKg/mOa4yUie2Olqg1Ghbi5JJRBZVU3rIgzWSObvIspxMA==} engines: {node: '>=18'} dependencies: - '@types/eslint': 8.56.8 + '@types/eslint': 8.56.9 ansi-escapes: 6.2.1 chalk: 5.3.0 eslint-rule-docs: 1.1.235 @@ -1673,7 +1673,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 7.6.0(eslint@8.57.0)(typescript@5.4.4) + '@typescript-eslint/parser': 7.6.0(eslint@8.57.0)(typescript@5.4.5) debug: 3.2.7 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 @@ -1732,7 +1732,7 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 7.6.0(eslint@8.57.0)(typescript@5.4.4) + '@typescript-eslint/parser': 7.6.0(eslint@8.57.0)(typescript@5.4.5) array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 @@ -1845,7 +1845,7 @@ packages: '@typescript-eslint/eslint-plugin': optional: true dependencies: - '@typescript-eslint/eslint-plugin': 7.6.0(@typescript-eslint/parser@7.6.0)(eslint@8.57.0)(typescript@5.4.4) + '@typescript-eslint/eslint-plugin': 7.6.0(@typescript-eslint/parser@7.6.0)(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 eslint-rule-composer: 0.3.0 dev: true @@ -3407,28 +3407,28 @@ packages: glob: 10.3.12 dev: true - /rollup@4.14.1: - resolution: {integrity: sha512-4LnHSdd3QK2pa1J6dFbfm1HN0D7vSK/ZuZTsdyUAlA6Rr1yTouUTL13HaDOGJVgby461AhrNGBS7sCGXXtT+SA==} + /rollup@4.14.2: + resolution: {integrity: sha512-WkeoTWvuBoFjFAhsEOHKRoZ3r9GfTyhh7Vff1zwebEFLEFjT1lG3784xEgKiTa7E+e70vsC81roVL2MP4tgEEQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true dependencies: '@types/estree': 1.0.5 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.14.1 - '@rollup/rollup-android-arm64': 4.14.1 - '@rollup/rollup-darwin-arm64': 4.14.1 - '@rollup/rollup-darwin-x64': 4.14.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.14.1 - '@rollup/rollup-linux-arm64-gnu': 4.14.1 - '@rollup/rollup-linux-arm64-musl': 4.14.1 - '@rollup/rollup-linux-powerpc64le-gnu': 4.14.1 - '@rollup/rollup-linux-riscv64-gnu': 4.14.1 - '@rollup/rollup-linux-s390x-gnu': 4.14.1 - '@rollup/rollup-linux-x64-gnu': 4.14.1 - '@rollup/rollup-linux-x64-musl': 4.14.1 - '@rollup/rollup-win32-arm64-msvc': 4.14.1 - '@rollup/rollup-win32-ia32-msvc': 4.14.1 - '@rollup/rollup-win32-x64-msvc': 4.14.1 + '@rollup/rollup-android-arm-eabi': 4.14.2 + '@rollup/rollup-android-arm64': 4.14.2 + '@rollup/rollup-darwin-arm64': 4.14.2 + '@rollup/rollup-darwin-x64': 4.14.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.14.2 + '@rollup/rollup-linux-arm64-gnu': 4.14.2 + '@rollup/rollup-linux-arm64-musl': 4.14.2 + '@rollup/rollup-linux-powerpc64le-gnu': 4.14.2 + '@rollup/rollup-linux-riscv64-gnu': 4.14.2 + '@rollup/rollup-linux-s390x-gnu': 4.14.2 + '@rollup/rollup-linux-x64-gnu': 4.14.2 + '@rollup/rollup-linux-x64-musl': 4.14.2 + '@rollup/rollup-win32-arm64-msvc': 4.14.2 + '@rollup/rollup-win32-ia32-msvc': 4.14.2 + '@rollup/rollup-win32-x64-msvc': 4.14.2 fsevents: 2.3.3 dev: true @@ -3864,13 +3864,13 @@ packages: hasBin: true dev: true - /ts-api-utils@1.3.0(typescript@5.4.4): + /ts-api-utils@1.3.0(typescript@5.4.5): resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} peerDependencies: typescript: '>=4.2.0' dependencies: - typescript: 5.4.4 + typescript: 5.4.5 dev: true /ts-interface-checker@0.1.13: @@ -3890,7 +3890,7 @@ packages: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} dev: true - /tsup@8.0.2(typescript@5.4.4): + /tsup@8.0.2(typescript@5.4.5): resolution: {integrity: sha512-NY8xtQXdH7hDUAZwcQdY/Vzlw9johQsaqf7iwZ6g1DOUlFYQ5/AtVAjTvihhEyeRlGo4dLRVHtrRaL35M1daqQ==} engines: {node: '>=18'} hasBin: true @@ -3919,11 +3919,11 @@ packages: joycon: 3.1.1 postcss-load-config: 4.0.2 resolve-from: 5.0.0 - rollup: 4.14.1 + rollup: 4.14.2 source-map: 0.8.0-beta.0 sucrase: 3.35.0 tree-kill: 1.2.2 - typescript: 5.4.4 + typescript: 5.4.5 transitivePeerDependencies: - supports-color - ts-node @@ -4024,10 +4024,10 @@ packages: peerDependencies: typedoc: 0.25.x dependencies: - typedoc: 0.25.13(typescript@5.4.4) + typedoc: 0.25.13(typescript@5.4.5) dev: true - /typedoc@0.25.13(typescript@5.4.4): + /typedoc@0.25.13(typescript@5.4.5): resolution: {integrity: sha512-pQqiwiJ+Z4pigfOnnysObszLiU3mVLWAExSPf+Mu06G/qsc3wzbuM56SZQvONhHLncLUhYzOVkjFFpFfL5AzhQ==} engines: {node: '>= 16'} hasBin: true @@ -4038,11 +4038,11 @@ packages: marked: 4.3.0 minimatch: 9.0.4 shiki: 0.14.7 - typescript: 5.4.4 + typescript: 5.4.5 dev: true - /typescript@5.4.4: - resolution: {integrity: sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==} + /typescript@5.4.5: + resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} engines: {node: '>=14.17'} hasBin: true dev: true @@ -4233,15 +4233,15 @@ packages: optional: true dependencies: '@eslint/eslintrc': 3.0.2 - '@typescript-eslint/eslint-plugin': 7.6.0(@typescript-eslint/parser@7.6.0)(eslint@8.57.0)(typescript@5.4.4) - '@typescript-eslint/parser': 7.6.0(eslint@8.57.0)(typescript@5.4.4) + '@typescript-eslint/eslint-plugin': 7.6.0(@typescript-eslint/parser@7.6.0)(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/parser': 7.6.0(eslint@8.57.0)(typescript@5.4.5) arrify: 3.0.0 - cosmiconfig: 8.3.6(typescript@5.4.4) + cosmiconfig: 8.3.6(typescript@5.4.5) define-lazy-prop: 3.0.0 eslint: 8.57.0 eslint-config-prettier: 9.1.0(eslint@8.57.0) eslint-config-xo: 0.44.0(eslint@8.57.0) - eslint-config-xo-typescript: 4.0.0(@typescript-eslint/eslint-plugin@7.6.0)(@typescript-eslint/parser@7.6.0)(eslint@8.57.0)(typescript@5.4.4) + eslint-config-xo-typescript: 4.0.0(@typescript-eslint/eslint-plugin@7.6.0)(@typescript-eslint/parser@7.6.0)(eslint@8.57.0)(typescript@5.4.5) eslint-formatter-pretty: 6.0.1 eslint-import-resolver-webpack: 0.13.8(eslint-plugin-import@2.29.1)(webpack@5.91.0) eslint-plugin-ava: 14.0.0(eslint@8.57.0) @@ -4267,7 +4267,7 @@ packages: semver: 7.6.0 slash: 5.1.0 to-absolute-glob: 3.0.0 - typescript: 5.4.4 + typescript: 5.4.5 webpack: 5.91.0(esbuild@0.19.12) transitivePeerDependencies: - '@types/eslint' diff --git a/src/result-async.ts b/src/result-async.ts index f7e44a2..aa5f1d2 100644 --- a/src/result-async.ts +++ b/src/result-async.ts @@ -103,6 +103,14 @@ type TraverseWithAllErrorsAsync = IsLiteralArray type Writable = T extends readonly unknown[] ? [...T] : T export class ResultAsync implements PromiseLike> { + static okAsync(value: T): ResultAsync { + return new ResultAsync(Promise.resolve(Result.ok(value))) + } + + static errAsync(error: E): ResultAsync { + return new ResultAsync(Promise.resolve(Result.err(error))) + } + static fromSafePromise(promise: PromiseLike): ResultAsync static fromSafePromise(promise: Promise): ResultAsync { const newPromise = promise @@ -146,6 +154,31 @@ export class ResultAsync implements PromiseLike> { ) as CombineResultsWithAllErrorsArrayAsync } + /** + * Wraps a async function with a try catch, creating a new function with the same + * arguments but returning `Ok` if successful, `Err` if the function throws + * + * @param fn function to wrap with ok on success or err on failure + * @param errorFn when an error is thrown, this will wrap the error result if provided + * @returns a new function that returns a `ResultAsync` + */ + static fromThrowable( + fn: (...args: A) => Promise, + errorFn?: (err: unknown) => E, + ): (...args: A) => ResultAsync { + return (...args) => new ResultAsync( + (async () => { + try { + const v = await fn(...args) + return Result.ok(v) + } catch (error) { + const e = errorFn ? errorFn(error) : error + return Result.err(e as E) + } + })(), + ) + } + private readonly innerPromise: Promise> constructor(res: Promise>) { @@ -257,45 +290,21 @@ export class ResultAsync implements PromiseLike> { } } -export function fromSafePromise(promise: PromiseLike): ResultAsync -export function fromSafePromise(promise: -| PromiseLike -| Promise): ResultAsync { - return ResultAsync.fromSafePromise(promise) -} - -export function fromPromise(promise: PromiseLike, errorFn: (e: unknown) => E): ResultAsync -export function fromPromise(promise: -| PromiseLike -| Promise, errorFn: (e: unknown) => E): ResultAsync { - return ResultAsync.fromPromise(promise, errorFn) -} - -export function okAsync(value: T): ResultAsync { - return new ResultAsync(Promise.resolve(Result.ok(value))) -} - -export function errAsync(error: E): ResultAsync { - return new ResultAsync(Promise.resolve(Result.err(error))) -} - -export function fromThrowableAsync( - fn: (...args: A) => Promise, - errorFn?: (err: unknown) => E, -): (...args: A) => ResultAsync { - return (...args) => new ResultAsync( - (async () => { - try { - const v = await fn(...args) - return Result.ok(v) - } catch (error) { - const e = errorFn ? errorFn(error) : error - return Result.err(e as E) - } - })(), - ) -} - +export const { okAsync, errAsync, fromPromise, fromSafePromise } = ResultAsync +export const fromThrowableAsync = ResultAsync.fromThrowable + +/** + * Evaluates the given generator to a Result returned or an Err yielded from it, + * whichever comes first. + * + * This function, in combination with `Result.safeUnwrap()`, is intended to emulate + * Rust's ? operator. + * See `/tests/safeTry.test.ts` for examples. + * + * @param body - What is evaluated. In body, `yield* result.safeUnwrap()` works as + * Rust's `result?` expression. + * @returns The first occurence of either an yielded Err or a returned `ResultAsync`. + */ export function safeTryAsync( body: () => AsyncGenerator, Result>, ): ResultAsync { diff --git a/src/result.ts b/src/result.ts index 73620fa..f13b91d 100644 --- a/src/result.ts +++ b/src/result.ts @@ -428,12 +428,7 @@ export class DisposableResult implements Resultable, Disposable { } } -export const ok = (value: T): Result => Result.ok(value) -export const err = (error: E): Result => Result.err(error) -export const fromThrowable = unknown, E>( - fn: Fn, - errorFn?: (e: any) => E, -): (...args: Parameters) => Result, E> => Result.fromThrowable(fn, errorFn) +export const { ok, err, fromThrowable } = Result /** * Evaluates the given generator to a Result returned or an Err yielded from it, diff --git a/tests/result-test.ts b/tests/result-test.ts index c156a9e..e9394ed 100644 --- a/tests/result-test.ts +++ b/tests/result-test.ts @@ -862,6 +862,19 @@ await describe('ResultAsync', async () => { } await describe('ResultAsync.fromThrowable', async () => { + const readFile = mock.fn(async (file: string) => { + if (file === 'foo.txt') { + return Buffer.from('foo') + } + + throw new Error('File not found') + }) + + const safeFileReader = fromThrowableAsync( + async (file: string) => readFile(file), + e => new Error('Oops: '.concat(String(e))), + ) + await it('creates a new function that returns a ResultAsync', async () => { const example = fromThrowableAsync(async (a: number, b: number) => a + b) const res = example(4, 8) @@ -912,6 +925,20 @@ await describe('ResultAsync', async () => { isTrue(val.isErr()) deepEqual(val._unsafeUnwrapErr(), new Error('Oops: No!')) }) + + await it('fromThrowableAsync for readfile ok', async () => { + const value = await safeFileReader('foo.txt') + .match(buffer => buffer.toString(), error => error.message) + + equal(value, 'foo') + }) + + await it('fromThrowableAsync for readfile not found', async () => { + const value = await safeFileReader('bar.txt') + .match(buffer => buffer.toString(), error => error.message) + + equal(value, 'Oops: Error: File not found') + }) }) }) From 422f3f1317b2cc9d0a82c587968241f846137894 Mon Sep 17 00:00:00 2001 From: Inaiat Moraes Date: Mon, 15 Apr 2024 10:34:32 -0300 Subject: [PATCH 2/3] feat: async finally --- jsr.json | 2 +- package.json | 2 +- src/result-async.ts | 29 ++++++++++ tests/result-test.ts | 122 +++++++++++++++++++++++++++++++------------ 4 files changed, 120 insertions(+), 35 deletions(-) diff --git a/jsr.json b/jsr.json index d35e0a0..481eca4 100644 --- a/jsr.json +++ b/jsr.json @@ -1,5 +1,5 @@ { "name": "@inaiat/resultar", - "version": "0.9.0", + "version": "1.0.0", "exports": "./src/index.ts" } \ No newline at end of file diff --git a/package.json b/package.json index 078f6e0..0f7ce4f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "resultar", - "version": "0.9.0", + "version": "1.0.0", "description": "Result pattern for typescript", "type": "module", "packageManager": "pnpm@8.15.6", diff --git a/src/result-async.ts b/src/result-async.ts index aa5f1d2..62cbc40 100644 --- a/src/result-async.ts +++ b/src/result-async.ts @@ -102,6 +102,21 @@ type TraverseWithAllErrorsAsync = IsLiteralArray // Converts a reaodnly array into a writable array type Writable = T extends readonly unknown[] ? [...T] : T +export class DisposableResultAsync implements PromiseLike> { + private readonly innerPromise: Promise> + + constructor(res: Promise>) { + this.innerPromise = res + } + + then( // eslint-disable-line unicorn/no-thenable + successCallback?: (res: Result) => A | PromiseLike, + failureCallback?: (reason: unknown) => B | PromiseLike, + ): PromiseLike { + return this.innerPromise.then(successCallback, failureCallback) + } +} + export class ResultAsync implements PromiseLike> { static okAsync(value: T): ResultAsync { return new ResultAsync(Promise.resolve(Result.ok(value))) @@ -282,6 +297,20 @@ export class ResultAsync implements PromiseLike> { ) } + finally(f: (value: T, error: E) => void): DisposableResultAsync { + return new DisposableResultAsync( + this.innerPromise.then(async res => { + try { + res.finally(f) + } catch { + // Dont do anything. Its just a finally + } + + return res + }), + ) + } + /** * Emulates Rust's `?` operator in `safeTry`'s body. See also `safeTry`. */ diff --git a/tests/result-test.ts b/tests/result-test.ts index e9394ed..546c705 100644 --- a/tests/result-test.ts +++ b/tests/result-test.ts @@ -3,12 +3,13 @@ import { describe, it, mock } from 'node:test' import assert, { equal, deepEqual, strictEqual, deepStrictEqual, notDeepStrictEqual, ok as isTrue, notEqual, } from 'node:assert' +import * as fs from 'node:fs/promises' import * as td from 'testdouble' import { Result, err, ok, } from '../src/result.js' import { - ResultAsync, errAsync, fromThrowableAsync, okAsync, + ResultAsync, errAsync, fromPromise, fromThrowableAsync, okAsync, } from '../src/result-async.js' const validateUser = (user: Readonly<{name: string}>): ResultAsync<{name: string}, string> => { @@ -247,25 +248,6 @@ await describe('Result.Ok', async () => { equal(mapped._unsafeUnwrap(), original) equal(sideEffect.mock.calls.length, 1) }) - - await it('Finally should be called', () => { - const closeFile = mock.fn(() => { - console.log('closing file') - }) - const file = { - file: 'file.txt', content: 'line 1', close: closeFile, - } - - const reader = ok(file) - - const result = reader.map(it => it.content).finally(_ => { - file.close() - }) - - isTrue(result.isOk()) - equal(result._unsafeUnwrap(), 'line 1') - equal(closeFile.mock.calls.length, 1) - }) }) await describe('Result.Err', async () => { @@ -398,19 +380,6 @@ await describe('Result.Err', async () => { equal(errorCallback.mock.calls.length, 1) }) }) - - await it('Finally should be called with error', () => { - const foo = err('foo') - const arrayResult = new Array() - foo.map(_p => 'boo').tap(x => x) - const result = foo.map(_p => 'boo').finally((x, y) => { - isTrue(x === undefined) - arrayResult.push(y, 'finalized') - }) - isTrue(result.isErr()) - equal(arrayResult.length, 2) - deepEqual(arrayResult, ['foo', 'finalized']) - }) }) await describe('ResultAsync', async () => { @@ -1040,6 +1009,93 @@ await describe('OrElse', async () => { }) }) +await describe('Finally', async () => { + interface FileSystem { + open: typeof fs.open; + close: fs.FileHandle['close']; + } + + const buffer = 'Not all those who wander are lost' // - J.R.R. Tolkien' + + await it('Finally should be called', () => { + const closeFile = mock.fn(() => { + console.log('closing file') + }) + const file = { + file: 'file.txt', content: 'line 1', close: closeFile, + } + + const reader = ok(file) + + const result = reader.map(it => it.content).finally(_ => { + file.close() + }) + + isTrue(result.isOk()) + equal(result._unsafeUnwrap(), 'line 1') + equal(closeFile.mock.calls.length, 1) + }) + + await it('Finally should be called with error', () => { + const foo = err('foo') + const arrayResult = new Array() + foo.map(_p => 'boo').tap(x => x) + const result = foo.map(_p => 'boo').finally((x, y) => { + isTrue(x === undefined) + arrayResult.push(y, 'finalized') + }) + isTrue(result.isErr()) + equal(arrayResult.length, 2) + deepEqual(arrayResult, ['foo', 'finalized']) + }) + + await it('Finally should be called with ok async', async () => { + const fileHandle = td.object() + const fileSystem = (await td.replaceEsm('node:fs/promises')).default as FileSystem + td.when(fileSystem.open('foo.txt', 'w')).thenResolve(fileHandle) + td.when(fileHandle.write(buffer)).thenResolve({ bytesWritten: 32, buffer }) + + const result + = await fromPromise(fileSystem.open('foo.txt', 'w'), String) + .andThen( + handle => fromPromise(handle.write(buffer), + String), + ) + .finally(async (v, _) => { + equal(v.buffer, buffer) + await fileHandle.close() + }) + + td.verify(fileHandle.close(), { times: 1 }) + + equal(result.isOk(), true) + equal(result._unsafeUnwrap().bytesWritten, 32) + }) + + await it('Finally should be called with error async', async () => { + const fileHandle = td.object() + const fileSystem = (await td.replaceEsm('node:fs/promises')).default as FileSystem + td.when(fileSystem.open('bar.txt', 'w')).thenResolve(fileHandle) + td.when(fileHandle.write(buffer)).thenReject(new Error('Oops: Error: EACCES: permission denied, open \'foo.txt\'')) + + const result + = await fromPromise(fileSystem.open('bar.txt', 'w'), String) + .andThen( + handle => fromPromise(handle.write(buffer), + String), + ) + .finally(async (_, e) => { + isTrue(typeof e === 'string') + await fileHandle.close() + }) + + td.verify(fileHandle.close(), { times: 1 }) + + equal(result.isErr(), true) + equal(result._unsafeUnwrapErr(), 'Error: Oops: Error: EACCES: permission denied, open \'foo.txt\'') + }) +}) + await describe('Utils', async () => { await describe('`Result.combine`', async () => { await describe('Synchronous `combine`', async () => { From a2d115b12a1ade2a01fdb7498deeb7f64252a6c9 Mon Sep 17 00:00:00 2001 From: Inaiat Moraes Date: Mon, 15 Apr 2024 10:55:48 -0300 Subject: [PATCH 3/3] 1.0.0 --- BUILD_SHA | 2 +- README.md | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/BUILD_SHA b/BUILD_SHA index 93b0e5c..f4bd7b8 100644 --- a/BUILD_SHA +++ b/BUILD_SHA @@ -1 +1 @@ -d3c9909935d9336f9c9848a07af4255c795a0915 +422f3f1317b2cc9d0a82c587968241f846137894 diff --git a/README.md b/README.md index 3e96bc9..1ff116b 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ For asynchronous tasks, `resultar` offers a `ResultAsync` class which wraps a `P - [`ResultAsync.andThen` (method)](#resultasyncandthen-method) - [`ResultAsync.orElse` (method)](#resultasyncorelse-method) - [`ResultAsync.tap` (method)](#resultasynctap-method) + - [`ResultAsync.finally` (method)](#resultasyncfinally-method) - [`ResultAsync.match` (method)](#resultasyncmatch-method) - [`ResultAsync.combine` (static class method)](#resultasynccombine-static-class-method) - [`ResultAsync.combineWithAllErrors` (static class method)](#resultasynccombinewithallerrors-static-class-method) @@ -1093,10 +1094,42 @@ class ResultAsync { } ``` + + **Example:** See [`Result.tap` (method)](#resulttap-method) +[⬆️ Back to top](#toc) + +--- + +#### `ResultAsync.finally` (method) +Executes a cleanup function wether the is ok or error. This method is usefull to cleanup resources for async operations. +**Signature:** +```typescript +class ResultAsync { + finally(f: (value: T, error: E) => void): DisposableResultAsync {...} +} +``` + +**Example:** +```typescript +const fileHandle = await fs.open('foo.txt', 'w') + +const result + = await fromPromise(fileHandle.write('A new line of text'), String) + .finally(async () => { + console.info('Closing file handle') + await fileHandle.close() + }) + +if (result.isOk()) { + console.log(result.value) // == 'A new line of text' +} + +``` + [⬆️ Back to top](#toc) ---