├── .babelrc ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── dist └── index.js ├── package.json ├── src └── index.js └── test └── index.spec.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-1"], 3 | "env": { 4 | "test": { 5 | "sourceMaps": "inline" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | .nyc_output 3 | coverage/ 4 | node_modules/ 5 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "rules": { 4 | "strict": 0, 5 | "indent": [2, 4, { "SwitchCase": 1 }] 6 | }, 7 | "extends": "airbnb" 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | jsconfig.json 2 | 3 | node_modules 4 | coverage 5 | .nyc_output 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Andrew McDowell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Async Error Catcher for ExpressJS routes 2 | ![https://api.travis-ci.org/madole/async-error-catcher.svg](https://api.travis-ci.org/madole/async-error-catcher.svg) 3 | 4 | A helper to automatically catch errors thrown within ExpressJS routes and then pass them to the `next` function so they can be picked up by your error handler at the end of the stack. 5 | 6 | 7 | ```javascript 8 | import express, { Router } from 'express'; 9 | import catchErrors from 'async-error-catcher'; 10 | 11 | const app = express(); 12 | const router = new Router(); 13 | 14 | async function route(req, res, next) { 15 | /* Do something asynchronous */ 16 | } 17 | 18 | router.get(catchErrors(route)); 19 | 20 | app.use(router); 21 | 22 | /* Error Handler middleware */ 23 | app.use((err, req, res, next) => { 24 | /* Handle Errors */ 25 | }); 26 | 27 | ``` 28 | 29 | This also works for any middleware or route function that returns a promise. 30 | 31 | For a more detailed breakdown, I've explained a bit more on my blog. 32 | 33 | http://madole.xyz/error-handling-in-express-with-async-await-routes/ 34 | 35 | _NOTE: Written in ES6, but compiled back to ES5 with babel_ 36 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = asyncErrorCatcher; 7 | function asyncErrorCatcher(fn) { 8 | if (!(fn instanceof Function)) { 9 | throw new Error('Must supply a function'); 10 | } 11 | 12 | return function (req, res, next) { 13 | for (var _len = arguments.length, rest = Array(_len > 3 ? _len - 3 : 0), _key = 3; _key < _len; _key++) { 14 | rest[_key - 3] = arguments[_key]; 15 | } 16 | 17 | var promise = fn.apply(undefined, [req, res, next].concat(rest)); 18 | if (!promise || !promise.catch) return; 19 | promise.catch(function (err) { 20 | return next(err); 21 | }); 22 | }; 23 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-error-catcher", 3 | "version": "1.0.1", 4 | "description": "Catch errors from async express routes and pass them to error catcher middleware", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "build": "babel src/ -d dist/", 8 | "test": "ava", 9 | "test-coverage": "nyc ava" 10 | }, 11 | "keywords": [], 12 | "author": "Andrew McDowell (http://madole.github.io/)", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "ava": "^0.15.2", 16 | "babel": "^6.5.2", 17 | "babel-cli": "^6.9.0", 18 | "babel-eslint": "^6.0.4", 19 | "babel-preset-es2015": "^6.9.0", 20 | "babel-preset-stage-1": "^6.5.0", 21 | "babel-register": "^6.9.0", 22 | "eslint": "^2.11.1", 23 | "eslint-config-airbnb": "^9.0.1", 24 | "eslint-plugin-import": "^1.8.1", 25 | "eslint-plugin-jsx-a11y": "^1.3.0", 26 | "eslint-plugin-react": "^5.1.1", 27 | "install": "^0.8.1", 28 | "npm": "^3.9.5", 29 | "nyc": "^6.4.4" 30 | }, 31 | "ava": { 32 | "files": [ 33 | "test/*.spec.js" 34 | ], 35 | "source": [ 36 | "src/*.js", 37 | "!dist/**/*" 38 | ], 39 | "failFast": true, 40 | "tap": true, 41 | "require": [ 42 | "babel-register" 43 | ], 44 | "babel": "inherit" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export default function asyncErrorCatcher(fn) { 2 | if (!(fn instanceof Function)) { 3 | throw new Error('Must supply a function'); 4 | } 5 | 6 | return (req, res, next, ...rest) => { 7 | const promise = fn(req, res, next, ...rest); 8 | if (!promise || !promise.catch) return; 9 | promise.catch(err => next(err)); 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /test/index.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import catchErrors from '../src/index'; 3 | 4 | test('errorCatcher', async t => { 5 | function route() { 6 | return Promise.reject('x'); 7 | } 8 | 9 | const routeWithCatcher = catchErrors(route); 10 | const req = {}; 11 | const res = {}; 12 | const next = err => { 13 | if (err) { 14 | t.pass('Err has been called'); 15 | } else { 16 | t.fail('no error passed'); 17 | } 18 | }; 19 | 20 | routeWithCatcher(req, res, next); 21 | }); 22 | 23 | test('should throw error if no fn passed in', t => { 24 | t.throws(catchErrors, Error); 25 | }); 26 | 27 | test('should throw error if object instead of a function is passed in', t => { 28 | t.throws(() => catchErrors({}), Error); 29 | }); 30 | 31 | test('should not throw error if function is passed in and it resolves', async t => { 32 | function route() { 33 | return Promise.resolve('x'); 34 | } 35 | 36 | const routeWithCatcher = catchErrors(route); 37 | const req = {}; 38 | const res = {}; 39 | const next = () => { 40 | t.fail('no error passed'); 41 | }; 42 | 43 | routeWithCatcher(req, res, next); 44 | }); 45 | 46 | test('should not throw error if non async function is passed in', t => { 47 | function route() { 48 | return true; 49 | } 50 | const routeWithCatcher = catchErrors(route); 51 | const req = {}; 52 | const res = {}; 53 | const next = () => { 54 | t.fail('no error passed'); 55 | }; 56 | routeWithCatcher(req, res, next); 57 | }); 58 | 59 | test('should not throw error if non async function is passed in and returns nothing', t => { 60 | function route() {} 61 | const routeWithCatcher = catchErrors(route); 62 | const req = {}; 63 | const res = {}; 64 | const next = () => { 65 | t.fail('no error passed'); 66 | }; 67 | routeWithCatcher(req, res, next); 68 | }); 69 | 70 | test('should handle extra route parameters', async t => { 71 | function route(req, res, next, param) { 72 | t.is(param, 'param'); 73 | return Promise.resolve('x'); 74 | } 75 | 76 | const routeWithCatcher = catchErrors(route); 77 | const req = {}; 78 | const res = {}; 79 | const next = () => { 80 | t.fail('no error passed'); 81 | }; 82 | 83 | routeWithCatcher(req, res, next, 'param'); 84 | }); 85 | --------------------------------------------------------------------------------