├── LICENSE ├── README.md ├── index.js ├── package.json └── test └── test.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Julian M 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 | # :traffic_light: TryInline 2 | > An easy inline error handling wrapper for async promises and syncronous functions 3 | 4 | [![NPM Version][npm-image]][npm-url] 5 | 6 | :bulb: _Inspired by [await-to-js](https://github.com/scopsy/await-to-js)_ 7 | 8 | ## :gift: Example 9 | ```js 10 | const { try_ } = require('try-inline'); 11 | 12 | let err, data; 13 | 14 | // async 15 | [err, data] = await try_(somePromise()); 16 | if (err) process.exit(1); 17 | console.log(data); 18 | 19 | // normal function 20 | [err, data] = try_(() => someFunction()); 21 | if (err) process.exit(1); 22 | console.log(data); 23 | ``` 24 | 25 | ## :package: Installation 26 | ```bash 27 | $ npm install try-inline 28 | ``` 29 | 30 | ## :barber: Features 31 | - Inline error catching 32 | - Configurable error logging 33 | - Error object patching 34 | - on a execution fail, the returned error object includes its _ErrorString_ 35 | - Labeling executions for better debugging 36 | - Filtering error results for specified key-paths 37 | (documentation coming soon) 38 | - only show specific keys from the error object 39 | 40 | ## :nut_and_bolt: API 41 | 42 | ### `try_(executionObj, logOptionsString, [options]) => [err, data]` 43 | 44 | Wraps an execution safely. The default TryInline instance. 45 | - `executionObj` - the object to execute. Can be a **promise** or a **callback with a syncronous function**. 46 | - `logOptionsString` - *optional* (you can leave it empty) option string for the logger. 47 | - Format: `"(logLevel:)labelString"` 48 | - `logLevel` - method used from logger. The default logger is the JavaScript global "console". So the available values are: _`info, log, warn, error`_. **Defaults** to `error`. When you want to use your own logger, take a look at creating your own TryInline custom instance. 49 | - `labelString` - optional label attached to the error log message. 50 | - Example: `"warn:HTTP_TIMEOUT"` -> Logger gets the '*warn*' log-level and the label string '*HTTP_TIMEOUT*' 51 | - `options` - optional object with: 52 | - `errData` - additional error information (assinged to `error.ErrorData`). 53 | 54 | **Returns** an array with two values: 55 | - `err` - the error obejct. When `executionObj` throws an error, it is assigned to `err`. Otherwise `err` is **null**. 56 | - `data` - returned value from `executionObj`. On error it gets **undefined**. 57 | 58 | ```js 59 | const { try_ } = require('try-inline'); 60 | 61 | let [err, data] = await try_(readFilePromise('lorem.txt'), 'warn:READ_FILE_ERR', 62 | { errData: "Safely ignore the error. The lorem file is optional." } 63 | }); 64 | 65 | // array destructuring is awesome! 66 | let [err] = ... // just get the error obj 67 | let [, data] = ... // only get the data obj 68 | ``` 69 | 70 | ### `new TryInline(options) => try_ (customized)` 71 | 72 | Creates a custom TryInline instance with specified options. 73 | - `options` - required object where: 74 | - `Logger` - custom error handling function. It gets _`error, level, label`_ passed as arguments. 75 | - `DefaultLogLevel` - set the default level for your Logger. 76 | 77 | **Returns** a custom *`try_`* instance with attached `Logger`. 78 | 79 | ```js 80 | const TryInline = require('try-inline'); 81 | 82 | const try_ = new TryInline({ 83 | Logger: function(error, level, label) { 84 | const logMessage = label ? `(${label}) ${error}` : error; 85 | console[level](logMessage); 86 | }, 87 | DefaultLogLevel: 'debug' 88 | }); 89 | ``` 90 | 91 | ## :point_up: Notes 92 | Do not always trust automatic semi-colon insertion (ASI) in JavaScript, when not using semi-colons! 93 | Be careful when assigning the output variables by destructuring the returned array! 94 | 95 | When you want to be `100% safe`, then put a semi-colon in front of the destructuring statement: 96 | ```js 97 | ;[err, data] = await try_(somePromise()) 98 | ``` 99 | 100 | ## :page_with_curl: License 101 | [MIT](https://github.com/DrJume/try-inline/blob/master/LICENSE) 102 | 103 | [npm-image]: https://img.shields.io/npm/v/try-inline.svg 104 | [npm-url]: https://www.npmjs.com/package/try-inline 105 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const filterObj = (objToFilter, allowedKeys) => 2 | allowedKeys.reduce((obj, keyString) => { 3 | obj[keyString] = 4 | keyString.split('.') 5 | .reduce((filteredObj, key) => (filteredObj ? filteredObj[key] : undefined), objToFilter) 6 | return obj 7 | }, {}) 8 | 9 | const isPromise = (objToCheck) => Promise.resolve(objToCheck) === objToCheck 10 | const isFunction = (objToCheck) => objToCheck && {}.toString.call(objToCheck) === '[object Function]' 11 | 12 | function DefaultLogger(error, level, label) { 13 | if (!console[level]) { 14 | level = 'error' 15 | } 16 | 17 | const displayErr = JSON.stringify(error, null, 2) 18 | 19 | const logString = label ? `(${label}) ${displayErr}` : displayErr 20 | console[level](logString) 21 | } 22 | 23 | function handleError(error, logLvl, labelString, errData, Logger) { 24 | const errorString = error.toString() 25 | 26 | const [label, ...allowedKeys] = labelString.split('#') 27 | if (allowedKeys.length > 0) { 28 | error = filterObj(error, allowedKeys) 29 | } 30 | 31 | Object.assign(error, { 32 | ErrorString: errorString, 33 | }) 34 | 35 | if (errData) { 36 | Object.assign(error, { 37 | ErrorData: errData, 38 | }) 39 | } 40 | 41 | Logger ? Logger(error, logLvl, label) : DefaultLogger(error, logLvl, label) 42 | 43 | return error 44 | } 45 | 46 | function tryWrapper( 47 | executionObj, 48 | logOptionsString = '', // format="(logLvl:)labelString" 49 | { errData } = {}, 50 | Logger, 51 | DefaultLogLevel = 'error' 52 | ) { 53 | const logOptions = logOptionsString.split(':') 54 | if (logOptions.length < 2) logOptions.unshift('') 55 | 56 | let [logLvl, labelString] = logOptions 57 | 58 | if (!logLvl) logLvl = DefaultLogLevel 59 | 60 | if (isPromise(executionObj)) { 61 | return executionObj 62 | .then(data => [null, data]) 63 | .catch(err => [(handleError(err, logLvl, labelString, errData, Logger)), undefined]) 64 | } 65 | 66 | if (isFunction(executionObj)) { 67 | try { 68 | const data = executionObj() 69 | return [null, data] 70 | } catch (err) { 71 | return [(handleError(err, logLvl, labelString, errData, Logger)), undefined] 72 | } 73 | } 74 | 75 | return undefined 76 | } 77 | 78 | module.exports = class { 79 | 80 | /** 81 | * Creates a custom TryInline instance with specified options. 82 | * 83 | * @param {Object} options - TryInline instance options. 84 | * @param {Logger} options.Logger - Custom error handling function. 85 | * @param {string} options.DefaultLogLevel - Set the default level for the logger. 86 | * @return {Function} The TryInline instance 87 | */ 88 | 89 | /** 90 | * @name Logger 91 | * @function 92 | * @param {Object} error - The passed error object. 93 | * @param {string} level - Log level. 94 | * @param {string} label - Optional label, can be attached to the error log message. 95 | */ 96 | constructor({ Logger, DefaultLogLevel }) { 97 | return (executionObj, logOptionsString, { errData }) => 98 | tryWrapper(executionObj, logOptionsString, { errData }, Logger, DefaultLogLevel) 99 | } 100 | 101 | /** 102 | * Wraps an execution safely. The default TryInline instance. 103 | * 104 | * @param {(Promise|Function)} executionObj - The object to execute. 105 | * @param {string} [logOptionsString] - Format is "(logLevel:)labelString" 106 | * @param {Object} [options] 107 | * @param {Object} [options.errData] - Additional error information (assinged to `error.ErrorData`). 108 | * @return {(Promise|Object[])} Returning an array with [err, data]. 109 | */ 110 | static try_(...args) { 111 | return tryWrapper(...args) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "try-inline", 3 | "version": "2.0.1", 4 | "engines": { 5 | "node": ">=8" 6 | }, 7 | "repository": "github:DrJume/try-inline", 8 | "description": "A functional approach to catching errors inline for async promises and syncronous functions", 9 | "main": "index.js", 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "author": "DrJume", 14 | "license": "MIT" 15 | } 16 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | process.on('unhandledRejection', (arg) => { 2 | console.log(arg) 3 | }); 4 | 5 | (async () => { 6 | const { try_ } = require('../index.js') 7 | const TryInline = require('../index.js') 8 | 9 | function somePromise(arg) { 10 | return new Promise((resolve, reject) => { 11 | setTimeout(() => { 12 | if (arg === 'err') { 13 | reject(new Error('ERRORSTRING')) 14 | } 15 | resolve(arg) 16 | }, 1000) 17 | }) 18 | } 19 | 20 | function someFunction(arg) { 21 | if (arg === 'err') { 22 | throw new Error('ERRORSTRING') 23 | } 24 | return arg 25 | } 26 | 27 | let err, data 28 | 29 | // async 30 | [err, data] = await try_(somePromise('err'), 'warn:SOME_PROMISE', { errData: 'errDataPromise' }); 31 | // if (err) process.exit(1); 32 | console.log(data); 33 | 34 | // normal function 35 | [err, data] = try_(() => someFunction('err'), 'info:SOME_FUNC', { errData: 'errDataSyncr' }); 36 | // if (err) process.exit(1); 37 | console.log(data); 38 | 39 | const myTry_ = new TryInline({ 40 | Logger(error, level, label) { 41 | console.log(JSON.stringify({ error, level, label }, null, 2)) 42 | }, 43 | DefaultLogLevel: 'debug' 44 | }); 45 | 46 | [err, data] = await myTry_(somePromise('err'), '', { errData: 'errDataPromise' }); 47 | console.log(data); 48 | 49 | })() 50 | --------------------------------------------------------------------------------