|
| 1 | +# eslint-plugin-no-throw-await |
| 2 | + |
| 3 | +Forces explicit error handling for promises (Similar to golang) through disallowing unpredictable awaits |
| 4 | + |
| 5 | +## Why? |
| 6 | + |
| 7 | +`await` can't be trusted. and try-catching everything is 💩. Explicit error handling is the way. It doesn't clutter your code it makes it better. Take a look at the following code: |
| 8 | +```typescript |
| 9 | +async function foo() { |
| 10 | + await bar() |
| 11 | +} |
| 12 | +``` |
| 13 | +Is `bar` safe to await? does it throw an exception maybe? Just in case let's wrap it in a try-catch |
| 14 | +```typescript |
| 15 | +async function foo() { |
| 16 | + try { |
| 17 | + await bar() |
| 18 | + } catch (e) { |
| 19 | + /* whatever */ |
| 20 | + } |
| 21 | +} |
| 22 | +``` |
| 23 | +Now assume you don't know what `foo` does. Or you don't want to read every async function line by line to check if it may throw an exception before using it. So what do you do? Also wrap `foo` in a try-catch just in case |
| 24 | +```typescript |
| 25 | +try { await foo() } catch (e) { } |
| 26 | +``` |
| 27 | +When/how do you propgate an error to the caller? or do you silence everything throuh a try catch? What if you have a series of async functions. But you don't want one throw to stop everything. Do you just wrap every single one in a try-catch. Or worse, use `.catch` nesting hell. There are many other examples of how bad this trycatching can get, amongst other issues with throwing in an async func. |
| 28 | + |
| 29 | +The goal of this plugin is to treat every promise as unsafe, which they are, and only allow awaiting a safe promise. A safe promise in this case means one that will not crash the application if left outside of a try-catch (will never throw). To to that, a linter rule will prevent you from awaiting a promise unless it's wrapped by a `awaitable` function. |
| 30 | + |
| 31 | +## awaitable |
| 32 | +A function that turns unsafe promises into safe promises. One implementation (golang like error handling): |
| 33 | +```typescript |
| 34 | +/** |
| 35 | + * Guarantees that a promise throw will be handled and returned gracefully as an error if any |
| 36 | + * The returned promise will never throw an exception |
| 37 | + * Result and error type can be specified through awaitable<ResultType, ErrorType> |
| 38 | + * @param fn Promise |
| 39 | + * @returns Promise<[result, error]> |
| 40 | + * - `result`: Returned on success, null on throw (Infered type of `fn`) |
| 41 | + * - `error`: Null on success, returned on throw (Default to Error) |
| 42 | + */ |
| 43 | +/* Modified version from karanpratapsingh */ |
| 44 | +async function awaitable<R, E = Error> ( |
| 45 | + fn: Promise<R> |
| 46 | +): Promise<[R | null, E | null]> { |
| 47 | + // eslint-disable-next-line no-try-catch/no-try-catch |
| 48 | + try { |
| 49 | + // eslint-disable-next-line no-try-catch/no-direct-await |
| 50 | + const data: R = await fn |
| 51 | + return [data, null] |
| 52 | + } catch (error: any) { |
| 53 | + return [null, error] |
| 54 | + } |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +## Example |
| 59 | +```typescript |
| 60 | +async function foo (): Promise<boolean> { |
| 61 | + throw new Error('Some error') |
| 62 | +} |
| 63 | + |
| 64 | +async function testing (): Promise<void> { |
| 65 | + const [result, err] = await awaitable(foo()) |
| 66 | + if (err != null) { |
| 67 | + console.log(err.message) |
| 68 | + return |
| 69 | + } |
| 70 | + |
| 71 | + // Do stuff with result |
| 72 | + console.log(result) |
| 73 | +} |
| 74 | +``` |
| 75 | + |
| 76 | + |
| 77 | +## Installation |
| 78 | + |
| 79 | +You'll first need to install [ESLint](https://eslint.org/): |
| 80 | + |
| 81 | +```sh |
| 82 | +npm i eslint --save-dev |
| 83 | +``` |
| 84 | + |
| 85 | +Next, install `eslint-plugin-no-throw-await`: |
| 86 | + |
| 87 | +```sh |
| 88 | +npm install eslint-plugin-no-throw-await --save-dev |
| 89 | +``` |
| 90 | + |
| 91 | +## Usage |
| 92 | + |
| 93 | +Add `no-throw-await` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix: |
| 94 | + |
| 95 | +```json |
| 96 | +{ |
| 97 | + "plugins": [ |
| 98 | + "no-throw-await" |
| 99 | + ] |
| 100 | +} |
| 101 | +``` |
| 102 | + |
| 103 | + |
| 104 | +Then configure the rule under the rules section. |
| 105 | + |
| 106 | +```json |
| 107 | +{ |
| 108 | + "rules": { |
| 109 | + "no-throw-await/no-direct-await": "error" |
| 110 | + } |
| 111 | +} |
| 112 | +``` |
| 113 | + |
| 114 | +## Rules |
| 115 | + |
| 116 | +<!-- begin auto-generated rules list --> |
| 117 | + |
| 118 | +🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\ |
| 119 | +💡 Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). |
| 120 | + |
| 121 | +| Name | Description | 🔧 | 💡 | |
| 122 | +| :----------------------------------------------- | :------------------------------------------------- | :- | :- | |
| 123 | +| [no-direct-await](docs/rules/no-direct-await.md) | Enforces using an await handler before every await | 🔧 | 💡 | |
| 124 | + |
| 125 | +<!-- end auto-generated rules list --> |
| 126 | + |
| 127 | + |
0 commit comments