├── .gitignore ├── .jshintrc ├── .travis.yml ├── README.md ├── build.js ├── cli.js ├── custom.js ├── errno.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ ] 3 | , "bitwise": false 4 | , "camelcase": false 5 | , "curly": false 6 | , "eqeqeq": false 7 | , "forin": false 8 | , "immed": false 9 | , "latedef": false 10 | , "noarg": true 11 | , "noempty": true 12 | , "nonew": true 13 | , "plusplus": false 14 | , "quotmark": true 15 | , "regexp": false 16 | , "undef": true 17 | , "unused": true 18 | , "strict": false 19 | , "trailing": true 20 | , "maxlen": 120 21 | , "asi": true 22 | , "boss": true 23 | , "debug": true 24 | , "eqnull": true 25 | , "esnext": true 26 | , "evil": true 27 | , "expr": true 28 | , "funcscope": false 29 | , "globalstrict": false 30 | , "iterator": false 31 | , "lastsemic": true 32 | , "laxbreak": true 33 | , "laxcomma": true 34 | , "loopfunc": true 35 | , "multistr": false 36 | , "onecase": false 37 | , "proto": false 38 | , "regexdash": false 39 | , "scripturl": true 40 | , "smarttabs": false 41 | , "shadow": false 42 | , "sub": true 43 | , "supernew": false 44 | , "validthis": true 45 | , "browser": true 46 | , "couch": false 47 | , "devel": false 48 | , "dojo": false 49 | , "mootools": false 50 | , "node": true 51 | , "nonstandard": true 52 | , "prototypejs": false 53 | , "rhino": false 54 | , "worker": true 55 | , "wsh": false 56 | , "nomen": false 57 | , "onevar": false 58 | , "passfail": false 59 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - 14 7 | - 12 8 | - 10 9 | 10 | arch: 11 | - amd64 12 | - ppc64le 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-errno 2 | 3 | > Better [libuv](https://github.com/libuv/libuv)/[Node.js](https://nodejs.org)/[io.js](https://iojs.org) error handling & reporting. Available in npm as *errno*. 4 | 5 | [![npm](https://img.shields.io/npm/v/errno.svg)](https://www.npmjs.com/package/errno) 6 | [![Build Status](https://secure.travis-ci.org/rvagg/node-errno.png)](http://travis-ci.org/rvagg/node-errno) 7 | [![npm](https://img.shields.io/npm/dm/errno.svg)](https://www.npmjs.com/package/errno) 8 | 9 | * [errno exposed](#errnoexposed) 10 | * [Custom errors](#customerrors) 11 | 12 | 13 | ## errno exposed 14 | 15 | Ever find yourself needing more details about Node.js errors? Me too, so *node-errno* contains the errno mappings direct from libuv so you can use them in your code. 16 | 17 | **By errno:** 18 | 19 | ```js 20 | require('errno').errno[3] 21 | // → { 22 | // "errno": 3, 23 | // "code": "EACCES", 24 | // "description": "permission denied" 25 | // } 26 | ``` 27 | 28 | **By code:** 29 | 30 | ```js 31 | require('errno').code.ENOTEMPTY 32 | // → { 33 | // "errno": 53, 34 | // "code": "ENOTEMPTY", 35 | // "description": "directory not empty" 36 | // } 37 | ``` 38 | 39 | **Make your errors more descriptive:** 40 | 41 | ```js 42 | var errno = require('errno') 43 | 44 | function errmsg(err) { 45 | var str = 'Error: ' 46 | // if it's a libuv error then get the description from errno 47 | if (errno.errno[err.errno]) 48 | str += errno.errno[err.errno].description 49 | else 50 | str += err.message 51 | 52 | // if it's a `fs` error then it'll have a 'path' property 53 | if (err.path) 54 | str += ' [' + err.path + ']' 55 | 56 | return str 57 | } 58 | 59 | var fs = require('fs') 60 | 61 | fs.readFile('thisisnotarealfile.txt', function (err, data) { 62 | if (err) 63 | console.log(errmsg(err)) 64 | }) 65 | ``` 66 | 67 | **Use as a command line tool:** 68 | 69 | ``` 70 | ~ $ errno 53 71 | { 72 | "errno": 53, 73 | "code": "ENOTEMPTY", 74 | "description": "directory not empty" 75 | } 76 | ~ $ errno EROFS 77 | { 78 | "errno": 56, 79 | "code": "EROFS", 80 | "description": "read-only file system" 81 | } 82 | ~ $ errno foo 83 | No such errno/code: "foo" 84 | ``` 85 | 86 | Supply no arguments for the full list. Error codes are processed case-insensitive. 87 | 88 | You will need to install with `npm install errno -g` if you want the `errno` command to be available without supplying a full path to the node_modules installation. 89 | 90 | 91 | ## Custom errors 92 | 93 | Use `errno.custom.createError()` to create custom `Error` objects to throw around in your Node.js library. Create error hierarchies so `instanceof` becomes a useful tool in tracking errors. Call-stack is correctly captured at the time you create an instance of the error object, plus a `cause` property will make available the original error object if you pass one in to the constructor. 94 | 95 | ```js 96 | var create = require('errno').custom.createError 97 | var MyError = create('MyError') // inherits from Error 98 | var SpecificError = create('SpecificError', MyError) // inherits from MyError 99 | var OtherError = create('OtherError', MyError) 100 | 101 | // use them! 102 | if (condition) throw new SpecificError('Eeek! Something bad happened') 103 | 104 | if (err) return callback(new OtherError(err)) 105 | ``` 106 | 107 | Also available is a `errno.custom.FilesystemError` with in-built access to errno properties: 108 | 109 | ```js 110 | fs.readFile('foo', function (err, data) { 111 | if (err) return callback(new errno.custom.FilesystemError(err)) 112 | // do something else 113 | }) 114 | ``` 115 | 116 | The resulting error object passed through the callback will have the following properties: `code`, `errno`, `path` and `message` will contain a descriptive human-readable message. 117 | 118 | ## Contributors 119 | 120 | * [bahamas10](https://github.com/bahamas10) (Dave Eddy) - Added CLI 121 | * [ralphtheninja](https://github.com/ralphtheninja) (Lars-Magnus Skog) 122 | 123 | ## Copyright & Licence 124 | 125 | *Copyright (c) 2012-2015 [Rod Vagg](https://github.com/rvagg) ([@rvagg](https://twitter.com/rvagg))* 126 | 127 | Made available under the MIT licence: 128 | 129 | Permission is hereby granted, free of charge, to any person obtaining a copy 130 | of this software and associated documentation files (the "Software"), to deal 131 | in the Software without restriction, including without limitation the rights 132 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 133 | copies of the Software, and to permit persons to whom the Software is furnished 134 | to do so, subject to the following conditions: 135 | 136 | The above copyright notice and this permission notice shall be included in all 137 | copies or substantial portions of the Software. 138 | 139 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 140 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 141 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 142 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 143 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 144 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 145 | SOFTWARE. 146 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var request = require('request') 4 | , fs = require('fs') 5 | 6 | , uvheadloc = 'https://raw.github.com/joyent/libuv/master/include/uv.h' 7 | , defreg = /^\s*XX\(\s*([\-\d]+),\s*([A-Z]+),\s*"([^"]*)"\s*\)\s*\\?$/ 8 | 9 | 10 | request(uvheadloc, function (err, response) { 11 | if (err) 12 | throw err 13 | 14 | var data, out 15 | 16 | data = response.body 17 | .split('\n') 18 | .map(function (line) { return line.match(defreg) }) 19 | .filter(function (match) { return match }) 20 | .map(function (match) { return { 21 | errno: parseInt(match[1], 10) 22 | , code: match[2] 23 | , description: match[3] 24 | }}) 25 | 26 | out = 'var all = module.exports.all = ' + JSON.stringify(data, 0, 1) + '\n\n' 27 | 28 | out += '\nmodule.exports.errno = {\n ' 29 | + data.map(function (e, i) { 30 | return '\'' + e.errno + '\': all[' + i + ']' 31 | }).join('\n , ') 32 | + '\n}\n\n' 33 | 34 | out += '\nmodule.exports.code = {\n ' 35 | + data.map(function (e, i) { 36 | return '\'' + e.code + '\': all[' + i + ']' 37 | }).join('\n , ') 38 | + '\n}\n\n' 39 | 40 | out += '\nmodule.exports.custom = require("./custom")(module.exports)\n' 41 | 42 | fs.writeFile('errno.js', out) 43 | }) -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var errno = require('./') 4 | , arg = process.argv[2] 5 | , data, code 6 | 7 | if (arg === undefined) { 8 | console.log(JSON.stringify(errno.code, null, 2)) 9 | process.exit(0) 10 | } 11 | 12 | if ((code = +arg) == arg) 13 | data = errno.errno[code] 14 | else 15 | data = errno.code[arg] || errno.code[arg.toUpperCase()] 16 | 17 | if (data) 18 | console.log(JSON.stringify(data, null, 2)) 19 | else { 20 | console.error('No such errno/code: "' + arg + '"') 21 | process.exit(1) 22 | } 23 | -------------------------------------------------------------------------------- /custom.js: -------------------------------------------------------------------------------- 1 | var prr = require('prr') 2 | 3 | function init (type, message, cause) { 4 | if (!!message && typeof message != 'string') { 5 | message = message.message || message.name 6 | } 7 | prr(this, { 8 | type : type 9 | , name : type 10 | // can be passed just a 'cause' 11 | , cause : typeof message != 'string' ? message : cause 12 | , message : message 13 | }, 'ewr') 14 | } 15 | 16 | // generic prototype, not intended to be actually used - helpful for `instanceof` 17 | function CustomError (message, cause) { 18 | Error.call(this) 19 | if (Error.captureStackTrace) 20 | Error.captureStackTrace(this, this.constructor) 21 | init.call(this, 'CustomError', message, cause) 22 | } 23 | 24 | CustomError.prototype = new Error() 25 | 26 | function createError (errno, type, proto) { 27 | var err = function (message, cause) { 28 | init.call(this, type, message, cause) 29 | //TODO: the specificity here is stupid, errno should be available everywhere 30 | if (type == 'FilesystemError') { 31 | this.code = this.cause.code 32 | this.path = this.cause.path 33 | this.errno = this.cause.errno 34 | this.message = 35 | (errno.errno[this.cause.errno] 36 | ? errno.errno[this.cause.errno].description 37 | : this.cause.message) 38 | + (this.cause.path ? ' [' + this.cause.path + ']' : '') 39 | } 40 | Error.call(this) 41 | if (Error.captureStackTrace) 42 | Error.captureStackTrace(this, err) 43 | } 44 | err.prototype = !!proto ? new proto() : new CustomError() 45 | return err 46 | } 47 | 48 | module.exports = function (errno) { 49 | var ce = function (type, proto) { 50 | return createError(errno, type, proto) 51 | } 52 | return { 53 | CustomError : CustomError 54 | , FilesystemError : ce('FilesystemError') 55 | , createError : ce 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /errno.js: -------------------------------------------------------------------------------- 1 | var all = module.exports.all = [ 2 | { 3 | errno: -2, 4 | code: 'ENOENT', 5 | description: 'no such file or directory' 6 | }, 7 | { 8 | errno: -1, 9 | code: 'UNKNOWN', 10 | description: 'unknown error' 11 | }, 12 | { 13 | errno: 0, 14 | code: 'OK', 15 | description: 'success' 16 | }, 17 | { 18 | errno: 1, 19 | code: 'EOF', 20 | description: 'end of file' 21 | }, 22 | { 23 | errno: 2, 24 | code: 'EADDRINFO', 25 | description: 'getaddrinfo error' 26 | }, 27 | { 28 | errno: 3, 29 | code: 'EACCES', 30 | description: 'permission denied' 31 | }, 32 | { 33 | errno: 4, 34 | code: 'EAGAIN', 35 | description: 'resource temporarily unavailable' 36 | }, 37 | { 38 | errno: 5, 39 | code: 'EADDRINUSE', 40 | description: 'address already in use' 41 | }, 42 | { 43 | errno: 6, 44 | code: 'EADDRNOTAVAIL', 45 | description: 'address not available' 46 | }, 47 | { 48 | errno: 7, 49 | code: 'EAFNOSUPPORT', 50 | description: 'address family not supported' 51 | }, 52 | { 53 | errno: 8, 54 | code: 'EALREADY', 55 | description: 'connection already in progress' 56 | }, 57 | { 58 | errno: 9, 59 | code: 'EBADF', 60 | description: 'bad file descriptor' 61 | }, 62 | { 63 | errno: 10, 64 | code: 'EBUSY', 65 | description: 'resource busy or locked' 66 | }, 67 | { 68 | errno: 11, 69 | code: 'ECONNABORTED', 70 | description: 'software caused connection abort' 71 | }, 72 | { 73 | errno: 12, 74 | code: 'ECONNREFUSED', 75 | description: 'connection refused' 76 | }, 77 | { 78 | errno: 13, 79 | code: 'ECONNRESET', 80 | description: 'connection reset by peer' 81 | }, 82 | { 83 | errno: 14, 84 | code: 'EDESTADDRREQ', 85 | description: 'destination address required' 86 | }, 87 | { 88 | errno: 15, 89 | code: 'EFAULT', 90 | description: 'bad address in system call argument' 91 | }, 92 | { 93 | errno: 16, 94 | code: 'EHOSTUNREACH', 95 | description: 'host is unreachable' 96 | }, 97 | { 98 | errno: 17, 99 | code: 'EINTR', 100 | description: 'interrupted system call' 101 | }, 102 | { 103 | errno: 18, 104 | code: 'EINVAL', 105 | description: 'invalid argument' 106 | }, 107 | { 108 | errno: 19, 109 | code: 'EISCONN', 110 | description: 'socket is already connected' 111 | }, 112 | { 113 | errno: 20, 114 | code: 'EMFILE', 115 | description: 'too many open files' 116 | }, 117 | { 118 | errno: 21, 119 | code: 'EMSGSIZE', 120 | description: 'message too long' 121 | }, 122 | { 123 | errno: 22, 124 | code: 'ENETDOWN', 125 | description: 'network is down' 126 | }, 127 | { 128 | errno: 23, 129 | code: 'ENETUNREACH', 130 | description: 'network is unreachable' 131 | }, 132 | { 133 | errno: 24, 134 | code: 'ENFILE', 135 | description: 'file table overflow' 136 | }, 137 | { 138 | errno: 25, 139 | code: 'ENOBUFS', 140 | description: 'no buffer space available' 141 | }, 142 | { 143 | errno: 26, 144 | code: 'ENOMEM', 145 | description: 'not enough memory' 146 | }, 147 | { 148 | errno: 27, 149 | code: 'ENOTDIR', 150 | description: 'not a directory' 151 | }, 152 | { 153 | errno: 28, 154 | code: 'EISDIR', 155 | description: 'illegal operation on a directory' 156 | }, 157 | { 158 | errno: 29, 159 | code: 'ENONET', 160 | description: 'machine is not on the network' 161 | }, 162 | { 163 | errno: 31, 164 | code: 'ENOTCONN', 165 | description: 'socket is not connected' 166 | }, 167 | { 168 | errno: 32, 169 | code: 'ENOTSOCK', 170 | description: 'socket operation on non-socket' 171 | }, 172 | { 173 | errno: 33, 174 | code: 'ENOTSUP', 175 | description: 'operation not supported on socket' 176 | }, 177 | { 178 | errno: 34, 179 | code: 'ENOENT', 180 | description: 'no such file or directory' 181 | }, 182 | { 183 | errno: 35, 184 | code: 'ENOSYS', 185 | description: 'function not implemented' 186 | }, 187 | { 188 | errno: 36, 189 | code: 'EPIPE', 190 | description: 'broken pipe' 191 | }, 192 | { 193 | errno: 37, 194 | code: 'EPROTO', 195 | description: 'protocol error' 196 | }, 197 | { 198 | errno: 38, 199 | code: 'EPROTONOSUPPORT', 200 | description: 'protocol not supported' 201 | }, 202 | { 203 | errno: 39, 204 | code: 'EPROTOTYPE', 205 | description: 'protocol wrong type for socket' 206 | }, 207 | { 208 | errno: 40, 209 | code: 'ETIMEDOUT', 210 | description: 'connection timed out' 211 | }, 212 | { 213 | errno: 41, 214 | code: 'ECHARSET', 215 | description: 'invalid Unicode character' 216 | }, 217 | { 218 | errno: 42, 219 | code: 'EAIFAMNOSUPPORT', 220 | description: 'address family for hostname not supported' 221 | }, 222 | { 223 | errno: 44, 224 | code: 'EAISERVICE', 225 | description: 'servname not supported for ai_socktype' 226 | }, 227 | { 228 | errno: 45, 229 | code: 'EAISOCKTYPE', 230 | description: 'ai_socktype not supported' 231 | }, 232 | { 233 | errno: 46, 234 | code: 'ESHUTDOWN', 235 | description: 'cannot send after transport endpoint shutdown' 236 | }, 237 | { 238 | errno: 47, 239 | code: 'EEXIST', 240 | description: 'file already exists' 241 | }, 242 | { 243 | errno: 48, 244 | code: 'ESRCH', 245 | description: 'no such process' 246 | }, 247 | { 248 | errno: 49, 249 | code: 'ENAMETOOLONG', 250 | description: 'name too long' 251 | }, 252 | { 253 | errno: 50, 254 | code: 'EPERM', 255 | description: 'operation not permitted' 256 | }, 257 | { 258 | errno: 51, 259 | code: 'ELOOP', 260 | description: 'too many symbolic links encountered' 261 | }, 262 | { 263 | errno: 52, 264 | code: 'EXDEV', 265 | description: 'cross-device link not permitted' 266 | }, 267 | { 268 | errno: 53, 269 | code: 'ENOTEMPTY', 270 | description: 'directory not empty' 271 | }, 272 | { 273 | errno: 54, 274 | code: 'ENOSPC', 275 | description: 'no space left on device' 276 | }, 277 | { 278 | errno: 55, 279 | code: 'EIO', 280 | description: 'i/o error' 281 | }, 282 | { 283 | errno: 56, 284 | code: 'EROFS', 285 | description: 'read-only file system' 286 | }, 287 | { 288 | errno: 57, 289 | code: 'ENODEV', 290 | description: 'no such device' 291 | }, 292 | { 293 | errno: 58, 294 | code: 'ESPIPE', 295 | description: 'invalid seek' 296 | }, 297 | { 298 | errno: 59, 299 | code: 'ECANCELED', 300 | description: 'operation canceled' 301 | } 302 | ] 303 | 304 | module.exports.errno = {} 305 | module.exports.code = {} 306 | 307 | all.forEach(function (error) { 308 | module.exports.errno[error.errno] = error 309 | module.exports.code[error.code] = error 310 | }) 311 | 312 | module.exports.custom = require('./custom')(module.exports) 313 | module.exports.create = module.exports.custom.createError 314 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "errno", 3 | "authors": [ 4 | "Rod Vagg @rvagg (https://github.com/rvagg)" 5 | ], 6 | "description": "libuv errno details exposed", 7 | "keywords": [ 8 | "errors", 9 | "errno", 10 | "libuv" 11 | ], 12 | "version": "1.0.0", 13 | "main": "errno.js", 14 | "dependencies": { 15 | "prr": "~1.0.1" 16 | }, 17 | "bin": { 18 | "errno": "./cli.js" 19 | }, 20 | "devDependencies": { 21 | "error-stack-parser": "^2.0.1", 22 | "inherits": "^2.0.3", 23 | "tape": "~4.8.0" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/rvagg/node-errno.git" 28 | }, 29 | "license": "MIT", 30 | "scripts": { 31 | "test": "node --use_strict test.js" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | , inherits = require('inherits') 3 | , ErrorStackParser = require('error-stack-parser') 4 | , errno = require('./') 5 | 6 | test('sanity checks', function (t) { 7 | t.ok(errno.all, 'errno.all not found') 8 | t.ok(errno.errno, 'errno.errno not found') 9 | t.ok(errno.code, 'errno.code not found') 10 | 11 | t.equal(errno.all.length, 60, 'found ' + errno.all.length + ', expected 60') 12 | t.equal(errno.errno['-1'], errno.all[1], 'errno -1 not second element') 13 | 14 | t.equal(errno.code['UNKNOWN'], errno.all[1], 'code UNKNOWN not second element') 15 | 16 | t.equal(errno.errno[1], errno.all[3], 'errno 1 not fourth element') 17 | 18 | t.equal(errno.code['EOF'], errno.all[3], 'code EOF not fourth element') 19 | t.end() 20 | }) 21 | 22 | test('custom errors', function (t) { 23 | const Cust = errno.create('FooNotBarError') 24 | const cust = new Cust('foo is not bar') 25 | 26 | t.equal(cust.name, 'FooNotBarError', 'correct custom name') 27 | t.equal(cust.type, 'FooNotBarError', 'correct custom type') 28 | t.equal(cust.message, 'foo is not bar', 'correct custom message') 29 | t.notOk(cust.cause, 'no cause') 30 | t.end() 31 | }) 32 | 33 | test('callstack', function (t) { 34 | const MyError = errno.create('MyError') 35 | 36 | function lastFunction (ErrorType, cb) { 37 | process.nextTick(cb, new ErrorType('oh noes!')) 38 | } 39 | 40 | function secondLastFunction (ErrorType, cb) { 41 | lastFunction(ErrorType, cb) 42 | } 43 | 44 | function testFrames (t) { 45 | return function (err) { 46 | const stack = ErrorStackParser.parse(err) 47 | t.same(stack[0].functionName, 'lastFunction', 'last stack frame ok') 48 | t.same(stack[1].functionName, 'secondLastFunction', 'second last stack frame ok') 49 | t.end() 50 | } 51 | } 52 | 53 | t.test('custom error, default prototype', function (t) { 54 | secondLastFunction(MyError, testFrames(t)) 55 | }) 56 | 57 | t.test('custom error, custom prototype', function (t) { 58 | const MyError2 = errno.create('MyError2', MyError) 59 | secondLastFunction(MyError2, testFrames(t)) 60 | }) 61 | 62 | t.test('custom error, using inheritance', function (t) { 63 | const CustomError = errno.custom.CustomError 64 | 65 | function MyError3 (message, cause) { 66 | CustomError.call(this, message, cause) 67 | } 68 | 69 | inherits(MyError3, CustomError) 70 | 71 | secondLastFunction(MyError3, testFrames(t)) 72 | }) 73 | }) 74 | 75 | test('error without message', function (t) { 76 | const Cust = errno.create('WriteError') 77 | const cust = new Cust({ 78 | code: 22, 79 | message: '', 80 | name: 'QuotaExceededError' 81 | }) 82 | 83 | t.equal(cust.name, 'WriteError', 'correct custom name') 84 | t.equal(cust.type, 'WriteError', 'correct custom type') 85 | t.equal(cust.message, 'QuotaExceededError', 'message is the name') 86 | t.notOk(cust.cause, 'no cause') 87 | t.end() 88 | }) 89 | --------------------------------------------------------------------------------