Skip to content

Commit

Permalink
Initial
Browse files Browse the repository at this point in the history
  • Loading branch information
wes4m committed Mar 31, 2023
0 parents commit abd5a89
Show file tree
Hide file tree
Showing 9 changed files with 4,177 additions and 0 deletions.
19 changes: 19 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"use strict";

module.exports = {
root: true,
extends: [
"eslint:recommended",
"plugin:eslint-plugin/recommended",
"plugin:node/recommended",
],
env: {
node: true,
},
overrides: [
{
files: ["tests/**/*.js"],
env: { mocha: true },
},
],
};
131 changes: 131 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp
.cache

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
.DS_Store
127 changes: 127 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# eslint-plugin-no-throw-await

Forces explicit error handling for promises (Similar to golang) through disallowing unpredictable awaits

## Why?

`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:
```typescript
async function foo() {
await bar()
}
```
Is `bar` safe to await? does it throw an exception maybe? Just in case let's wrap it in a try-catch
```typescript
async function foo() {
try {
await bar()
} catch (e) {
/* whatever */
}
}
```
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
```typescript
try { await foo() } catch (e) { }
```
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.

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.

## awaitable
A function that turns unsafe promises into safe promises. One implementation (golang like error handling):
```typescript
/**
* Guarantees that a promise throw will be handled and returned gracefully as an error if any
* The returned promise will never throw an exception
* Result and error type can be specified through awaitable<ResultType, ErrorType>
* @param fn Promise
* @returns Promise<[result, error]>
* - `result`: Returned on success, null on throw (Infered type of `fn`)
* - `error`: Null on success, returned on throw (Default to Error)
*/
/* Modified version from karanpratapsingh */
async function awaitable<R, E = Error> (
fn: Promise<R>
): Promise<[R | null, E | null]> {
// eslint-disable-next-line no-try-catch/no-try-catch
try {
// eslint-disable-next-line no-try-catch/no-direct-await
const data: R = await fn
return [data, null]
} catch (error: any) {
return [null, error]
}
}
```

## Example
```typescript
async function foo (): Promise<boolean> {
throw new Error('Some error')
}

async function testing (): Promise<void> {
const [result, err] = await awaitable(foo())
if (err != null) {
console.log(err.message)
return
}

// Do stuff with result
console.log(result)
}
```


## Installation

You'll first need to install [ESLint](https://eslint.org/):

```sh
npm i eslint --save-dev
```

Next, install `eslint-plugin-no-throw-await`:

```sh
npm install eslint-plugin-no-throw-await --save-dev
```

## Usage

Add `no-throw-await` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix:

```json
{
"plugins": [
"no-throw-await"
]
}
```


Then configure the rule under the rules section.

```json
{
"rules": {
"no-throw-await/no-direct-await": "error"
}
}
```

## Rules

<!-- begin auto-generated rules list -->

🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
💡 Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).

| Name | Description | 🔧 | 💡 |
| :----------------------------------------------- | :------------------------------------------------- | :- | :- |
| [no-direct-await](docs/rules/no-direct-await.md) | Enforces using an await handler before every await | 🔧 | 💡 |

<!-- end auto-generated rules list -->


35 changes: 35 additions & 0 deletions docs/rules/no-direct-await.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Enforces using an await handler before every await (`no-throw-await/no-direct-await`)

🔧💡 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).

<!-- end auto-generated rule header -->

The goal of this rule is to provide a way of error handling that is similar to golang. [Why?](../../README.md#Why)

## Rule Details

This rule aims to enforces using of an await handler before every await

Examples of **incorrect** code for this rule:

```js
// 1
await someAsyncFunc()
// 2
const aPromise = new Promise()
await aPromise
// 3
await new Promise()
```

Examples of **correct** code for this rule:

```js
// 1
await awaitable(someAsyncFunc())
// 2
const aPromise = new Promise()
await awaitable(aPromise)
// 3
await awaitable(new Promise())
```
19 changes: 19 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* @fileoverview Forces explicit error handling through disallowing try-catch, and regular awaits.
* @author wes4m
*/
"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const requireIndex = require("requireindex");

//------------------------------------------------------------------------------
// Plugin Definition
//------------------------------------------------------------------------------


// import all rules in lib/rules
module.exports.rules = requireIndex(__dirname + "/rules");
Loading

0 comments on commit abd5a89

Please sign in to comment.