├── .gitignore ├── ERR.js ├── LICENSE.txt ├── README.md ├── example.js ├── example_without.js ├── package-lock.json ├── package.json └── tests.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /ERR.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 2011 Peter 'Pita' Martischka 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS-IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var header = "Async Stacktrace:"; 18 | 19 | module.exports = function (err, callback) 20 | { 21 | //there is a error 22 | if(err != null) 23 | { 24 | //if there is already a stacktrace avaiable 25 | if(err.stack != null) 26 | { 27 | //split stack by line 28 | var stackParts = err.stack.split("\n"); 29 | 30 | //check if there is already a header set, if not add one and a empty line 31 | if(stackParts[0] != header) 32 | { 33 | stackParts.unshift(header,""); 34 | } 35 | 36 | //add a new stacktrace line 37 | var asyncStackLine = new Error().stack.split("\n")[2]; 38 | stackParts.splice(1,0,asyncStackLine); 39 | 40 | //join the stacktrace 41 | err.stack = stackParts.join("\n"); 42 | } 43 | //no stacktrace, so lets create an error out of this object 44 | else 45 | { 46 | err = new Error(err); 47 | } 48 | 49 | //there is a callback, lets call it 50 | if(callback != null) 51 | { 52 | callback(err); 53 | } 54 | //no callback, throw the error 55 | else 56 | { 57 | throw err; 58 | } 59 | } 60 | 61 | //return true if an error happend 62 | return err != null; 63 | } 64 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2021 Peter 'Pita' Martischka 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Why classic stacktraces are not very helpful when dealing with async functions 2 | 3 | Look at this example. `one` calls `two`, `two` calls `three`, and `three` calls `four`. All functions call the given callback asynchronous. `four` calls the callback with an error. `three` and `two` passes the error to their callback function and stop executing with `return`. `one` finally throws it 4 | 5 | ```js 6 | function one() 7 | { 8 | two(function(err){ 9 | if(err){ 10 | throw err; 11 | } 12 | 13 | console.log("two finished"); 14 | }); 15 | } 16 | 17 | function two(callback) 18 | { 19 | setTimeout(function () { 20 | three(function(err) 21 | { 22 | if(err) { 23 | callback(err); 24 | return; 25 | } 26 | 27 | console.log("three finished"); 28 | callback(); 29 | }); 30 | }, 0); 31 | } 32 | 33 | function three(callback) 34 | { 35 | setTimeout(function () { 36 | four(function(err) 37 | { 38 | if(err) { 39 | callback(err); 40 | return; 41 | } 42 | 43 | console.log("four finished"); 44 | callback(); 45 | }); 46 | }, 0); 47 | } 48 | 49 | function four(callback) 50 | { 51 | setTimeout(function(){ 52 | callback(new Error()); 53 | }, 0); 54 | } 55 | 56 | one(); 57 | ``` 58 | 59 | ### When you execute it, you will get this: 60 | 61 | ``` 62 | $ node example_without.js 63 | 64 | /home/pita/Code/async-stacktrace/example_without.js:5 65 | throw err; 66 | ^ 67 | Error 68 | at Timer.callback (/home/pita/Code/async-stacktrace/example_without.js:47:14) 69 | ``` 70 | 71 | ### The problems here are: 72 | 73 | * You can see that the error happend in `four`, but you can't see from where `four` was called. The context gets lost 74 | * You write the same 4 lines over and over again, just to handle errors 75 | 76 | ## The solution 77 | 78 | ### Lets replace this code in `two` and `three` 79 | 80 | ```js 81 | if(err) { 82 | callback(err); 83 | return; 84 | } 85 | ``` 86 | 87 | ### with 88 | 89 | ```js 90 | if(ERR(err, callback)) return; 91 | ``` 92 | 93 | ### and replace this code in `one` 94 | 95 | ```js 96 | if(err){ 97 | throw err; 98 | } 99 | ``` 100 | 101 | ### with 102 | 103 | ```js 104 | ERR(err); 105 | ``` 106 | 107 | ### This is how it looks like now: 108 | 109 | ```js 110 | var ERR = require("async-stacktrace"); 111 | 112 | function one() 113 | { 114 | two(function(err){ 115 | ERR(err); 116 | 117 | console.log("two finished"); 118 | }); 119 | } 120 | 121 | function two(callback) 122 | { 123 | setTimeout(function () { 124 | three(function(err) 125 | { 126 | if(ERR(err, callback)) return; 127 | 128 | console.log("three finished"); 129 | callback(); 130 | }); 131 | }, 0); 132 | } 133 | 134 | function three(callback) 135 | { 136 | setTimeout(function () { 137 | four(function(err) 138 | { 139 | if(ERR(err, callback)) return; 140 | 141 | console.log("four finished"); 142 | callback(); 143 | }); 144 | }, 0); 145 | } 146 | 147 | function four(callback) 148 | { 149 | setTimeout(function(){ 150 | callback(new Error()); 151 | }, 0); 152 | } 153 | 154 | one(); 155 | ``` 156 | 157 | ### When you execute it, you will get this: 158 | 159 | ``` 160 | $ node example.js 161 | 162 | /home/pita/Code/async-stacktrace/ERR.js:57 163 | throw err; 164 | ^ 165 | Async Stacktrace: 166 | at /home/pita/Code/async-stacktrace/example.js:6:6 167 | at /home/pita/Code/async-stacktrace/example.js:17:10 168 | at /home/pita/Code/async-stacktrace/example.js:30:10 169 | 170 | Error 171 | at Timer.callback (/home/pita/Code/async-stacktrace/example.js:41:14) 172 | ``` 173 | 174 | ### What is new? 175 | 176 | The "Async Stacktrace" shows you where this error was caught and passed to the next callback. This allows you to see from where `four` was called. You also have less code to write 177 | 178 | ## npm 179 | ``` 180 | npm install async-stacktrace 181 | ``` 182 | 183 | ## Usage 184 | 185 | This is how you require the ERR function 186 | 187 | ```js 188 | var ERR = require("async-stacktrace"); 189 | ``` 190 | 191 | The parameters of `ERR()` are: 192 | 193 | 1. `err` The error object (can be a string that describes the error too) 194 | 2. `callback` (optional) If the callback is set and an error is passed, it will call the callback with the modified stacktrace. Else it will throw the error 195 | 196 | The return value is true if there is an error. Else its false 197 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var ERR = require("./ERR"); 2 | 3 | function one() 4 | { 5 | two(function(err){ 6 | ERR(err); 7 | 8 | console.log("two finished"); 9 | }); 10 | } 11 | 12 | function two(callback) 13 | { 14 | setTimeout(function () { 15 | three(function(err) 16 | { 17 | if(ERR(err, callback)) return; 18 | 19 | console.log("three finished"); 20 | callback(); 21 | }); 22 | }, 0); 23 | } 24 | 25 | function three(callback) 26 | { 27 | setTimeout(function () { 28 | four(function(err) 29 | { 30 | if(ERR(err, callback)) return; 31 | 32 | console.log("four finished"); 33 | callback(); 34 | }); 35 | }, 0); 36 | } 37 | 38 | function four(callback) 39 | { 40 | setTimeout(function(){ 41 | callback(new Error()); 42 | }, 0); 43 | } 44 | 45 | one(); 46 | -------------------------------------------------------------------------------- /example_without.js: -------------------------------------------------------------------------------- 1 | function one() 2 | { 3 | two(function(err){ 4 | if(err){ 5 | throw err; 6 | } 7 | 8 | console.log("two finished"); 9 | }); 10 | } 11 | 12 | function two(callback) 13 | { 14 | setTimeout(function () { 15 | three(function(err) 16 | { 17 | if(err) { 18 | callback(err); 19 | return; 20 | } 21 | 22 | console.log("three finished"); 23 | callback(); 24 | }); 25 | }, 0); 26 | } 27 | 28 | function three(callback) 29 | { 30 | setTimeout(function () { 31 | four(function(err) 32 | { 33 | if(err) { 34 | callback(err); 35 | return; 36 | } 37 | 38 | console.log("four finished"); 39 | callback(); 40 | }); 41 | }, 0); 42 | } 43 | 44 | function four(callback) 45 | { 46 | setTimeout(function(){ 47 | callback(new Error()); 48 | }, 0); 49 | } 50 | 51 | one(); 52 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-stacktrace", 3 | "version": "0.0.2", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "version": "0.0.2", 9 | "license": "MIT", 10 | "devDependencies": { 11 | "vows": "0.6.0" 12 | } 13 | }, 14 | "node_modules/eyes": { 15 | "version": "0.1.8", 16 | "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", 17 | "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=", 18 | "dev": true, 19 | "engines": { 20 | "node": "> 0.1.90" 21 | } 22 | }, 23 | "node_modules/vows": { 24 | "version": "0.6.0", 25 | "resolved": "https://registry.npmjs.org/vows/-/vows-0.6.0.tgz", 26 | "integrity": "sha1-vi8GgAnTmjezevhcKfqG2Fc9tDE=", 27 | "dev": true, 28 | "dependencies": { 29 | "eyes": ">=0.1.6" 30 | }, 31 | "bin": { 32 | "vows": "bin/vows" 33 | }, 34 | "engines": { 35 | "node": ">=0.2.6" 36 | } 37 | } 38 | }, 39 | "dependencies": { 40 | "eyes": { 41 | "version": "0.1.8", 42 | "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", 43 | "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=", 44 | "dev": true 45 | }, 46 | "vows": { 47 | "version": "0.6.0", 48 | "resolved": "https://registry.npmjs.org/vows/-/vows-0.6.0.tgz", 49 | "integrity": "sha1-vi8GgAnTmjezevhcKfqG2Fc9tDE=", 50 | "dev": true, 51 | "requires": { 52 | "eyes": ">=0.1.6" 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-stacktrace", 3 | "description": "Improves node.js stacktraces and makes it easier to handle errors", 4 | "homepage": "https://github.com/Pita/async-stacktrace", 5 | "author": "Peter 'Pita' Martischka ", 6 | "devDependencies": { 7 | "vows": "0.6.0" 8 | }, 9 | "main": "./ERR", 10 | "license": "MIT", 11 | "version": "0.0.2" 12 | } 13 | -------------------------------------------------------------------------------- /tests.js: -------------------------------------------------------------------------------- 1 | var vows = require('vows'); 2 | var assert = require('assert'); 3 | var ERR = require("./ERR"); 4 | 5 | var emptyFunc = function(){}; 6 | 7 | vows.describe('ERR function').addBatch({ 8 | 'when its loaded' : { 9 | topic: function() { 10 | return require("./ERR"); 11 | }, 12 | 13 | 'it returns the ERR function': function(topic){ 14 | assert.equal(typeof topic, "function"); 15 | }, 16 | 17 | 'its not global': function(topic){ 18 | assert.isUndefined(global.ERR); 19 | } 20 | } 21 | }).addBatch({ 22 | 'when you call it without an error' : { 23 | 'it returns false' : function() { 24 | assert.isFalse(ERR()); 25 | } 26 | }, 27 | 'when you call it with an error' : { 28 | 'it returns true' : function() { 29 | assert.isTrue(ERR(new Error(), emptyFunc)); 30 | } 31 | }, 32 | 'when you give it a callback and an error': { 33 | 'it calls the callback with the error' : function() { 34 | ERR(new Error(), function(err) { 35 | assert.isNotNull(err); 36 | }); 37 | } 38 | }, 39 | 'when you give it no callback and an error': { 40 | 'it throws the error' : function() { 41 | var wasThrown = false; 42 | 43 | try 44 | { 45 | ERR(new Error()); 46 | } 47 | catch(e) 48 | { 49 | wasThrown = true; 50 | } 51 | 52 | assert.isTrue(wasThrown); 53 | } 54 | }, 55 | 'when you call it with a string as an error' : { 56 | 'it uses the string as an error message' : function(){ 57 | var errStr = "testerror"; 58 | var err; 59 | ERR(errStr, function(_err){ 60 | err = _err; 61 | }); 62 | 63 | assert.equal(err.message, errStr); 64 | } 65 | }, 66 | 'when you give it a non-async stacktrace': { 67 | 'it turns it into an async stacktrace' : function(){ 68 | var errorBefore = new Error(); 69 | var stackLinesBefore = errorBefore.stack.split("\n").length; 70 | var errorAfter; 71 | 72 | ERR(errorBefore, function(err){ 73 | errorAfter = err; 74 | }); 75 | 76 | var stackLinesAfter = errorAfter.stack.split("\n").length; 77 | 78 | assert.equal(stackLinesBefore+3, stackLinesAfter); 79 | } 80 | }, 81 | 'when you give it a async stacktrace': { 82 | 'it adds a new stack line' : function(){ 83 | var err = new Error(); 84 | 85 | ERR(err, function(_err){ 86 | err = _err; 87 | }); 88 | 89 | var stackLinesBefore = err.stack.split("\n").length; 90 | 91 | ERR(err, function(_err){ 92 | err = _err; 93 | }); 94 | 95 | var stackLinesAfter = err.stack.split("\n").length; 96 | 97 | assert.equal(stackLinesBefore+1, stackLinesAfter); 98 | } 99 | } 100 | }).run(); 101 | --------------------------------------------------------------------------------