1 | # lazy-ass
|
2 |
|
3 | > Lazy assertions without performance penalty
|
4 |
|
5 | [![NPM][lazy-ass-icon] ][lazy-ass-url]
|
6 |
|
7 | [![Build status][lazy-ass-ci-image] ][lazy-ass-ci-url]
|
8 | [![manpm](https://img.shields.io/badge/manpm-compatible-3399ff.svg)](https://github.com/bahmutov/manpm)
|
9 | [![dependencies][lazy-ass-dependencies-image] ][lazy-ass-dependencies-url]
|
10 | [![devdependencies][lazy-ass-devdependencies-image] ][lazy-ass-devdependencies-url]
|
11 |
|
12 | [![semantic-release][semantic-image] ][semantic-url]
|
13 | [![Coverage Status][lazy-ass-coverage-image]][lazy-ass-coverage-url]
|
14 | [![Codacy][lazy-ass-codacy-image]][lazy-ass-codacy-url]
|
15 | [![Code Climate][lazy-ass-code-climate-image]][lazy-ass-code-climate-url]
|
16 |
|
17 | [Demo](http://glebbahmutov.com/lazy-ass/)
|
18 |
|
19 | Is the current code breaking dependencies if released?
|
20 | [![Dont-break][circle-ci-image] ][circle-ci-url] - checks using
|
21 | [dont-break](https://github.com/bahmutov/dont-break)
|
22 | [circle-ci-image]: https://circleci.com/gh/bahmutov/lazy-ass.svg?style=svg
|
23 | [circle-ci-url]: https://circleci.com/gh/bahmutov/lazy-ass
|
24 |
|
25 | ## Example
|
26 |
|
27 | Regular assertions evaluate all arguments and concatenate message
|
28 | EVERY time, even if the condition is true.
|
29 |
|
30 | ```js
|
31 | console.assert(typeof foo === 'object',
|
32 | 'expected ' + JSON.stringify(foo, null, 2) + ' to be an object');
|
33 | ```
|
34 |
|
35 | Lazy assertion function evaluates its arguments and forms
|
36 | a message ONLY IF the condition is false
|
37 |
|
38 | ```js
|
39 | lazyAss(typeof foo === 'object', 'expected', foo, 'to be an object');
|
40 | ```
|
41 |
|
42 | Concatenates strings, stringifies objects, calls functions - only if
|
43 | condition is false.
|
44 |
|
45 | ```js
|
46 | function environment() {
|
47 | // returns string
|
48 | }
|
49 | var user = {} // an object
|
50 | lazyAsync(condition, 'something went wrong for', user, 'in', environment);
|
51 | // throws an error with message equivalent of
|
52 | // 'something went wrong for ' + JSON.stringify(user) + ' in ' + environment()
|
53 | ```
|
54 |
|
55 | ## Why?
|
56 |
|
57 | * Passing an object reference to a function is about
|
58 | [2000-3000 times faster](http://jsperf.com/object-json-stringify)
|
59 | than serializing an object and passing it as a string.
|
60 | * Concatenating 2 strings before passing to a function is about
|
61 | [30% slower](http://jsperf.com/string-concat-vs-pass-string-reference)
|
62 | than passing 2 separate strings.
|
63 |
|
64 | ## Install
|
65 |
|
66 | Node: `npm install lazy-ass --save` then `var la = require('lazy-ass');`.
|
67 | You can attach the methods to the global object using
|
68 | `require('lazy-ass').globalRegister();`.
|
69 |
|
70 | Browser: `bower install lazy-ass --save`, include `index.js`,
|
71 | attaches functions `lazyAss` and `la` to `window` object.
|
72 |
|
73 | ## Notes
|
74 |
|
75 | You can pass as many arguments to *lazyAss* after the condition. The condition
|
76 | will be evaluated every time (this is required for any assertion). The rest of arguments
|
77 | will be concatenated according to rules
|
78 |
|
79 | * string will be left unchanged.
|
80 | * function will be called and its output will be concatenated.
|
81 | * any array or object will be JSON stringified.
|
82 |
|
83 | There will be single space between the individual parts.
|
84 |
|
85 | ## Lazy async assertions
|
86 |
|
87 | Sometimes you do not want to throw an error synchronously, breaking the entire
|
88 | execution stack. Instead you can throw an error asynchronously using `lazyAssync`,
|
89 | which internally implements logic like this:
|
90 |
|
91 | ```js
|
92 | if (!condition) {
|
93 | setTimeout(function () {
|
94 | throw new Error('Conditions is false!');
|
95 | }, 0);
|
96 | }
|
97 | ```
|
98 |
|
99 | This allows the execution to continue, while your global error handler (like
|
100 | my favorite [Sentry](http://glebbahmutov.com/blog/know-unknown-unknowns-with-sentry/))
|
101 | can still forward the error with all specified information to your server.
|
102 |
|
103 | ```js
|
104 | lazyAss.async(false, 'foo');
|
105 | console.log('after assync');
|
106 | // output
|
107 | after assync
|
108 | Uncaught Error: foo
|
109 | ```
|
110 |
|
111 | In this case, there is no meaningful error stack, so use good message
|
112 | arguments - there is no performance penalty!
|
113 |
|
114 | ## Rethrowing errors
|
115 |
|
116 | If the condition itself is an instance of Error, it is simply rethrown (synchronously or
|
117 | asynchronously).
|
118 |
|
119 | ```js
|
120 | lazyAss(new Error('foo'));
|
121 | // Uncaught Error: foo
|
122 | ```
|
123 |
|
124 | Useful to make sure errors in the promise chains are
|
125 | [not silently ignored](https://glebbahmutov.com/blog/why-promises-need-to-be-done/).
|
126 |
|
127 | For example, a rejected promise below this will be ignored.
|
128 |
|
129 | ```js
|
130 | var p = new Promise(function (resolve, reject) {
|
131 | reject(new Error('foo'));
|
132 | });
|
133 | p.then(...);
|
134 | ```
|
135 |
|
136 | We can catch it and rethrow it *synchronously*, but it will be ignored too (same way,
|
137 | only one step further)
|
138 |
|
139 | ```js
|
140 | var p = new Promise(function (resolve, reject) {
|
141 | reject(new Error('foo'));
|
142 | });
|
143 | p.then(..., lazyAss);
|
144 | ```
|
145 |
|
146 | But we can actually trigger global error if we rethrow the error *asynchronously*
|
147 |
|
148 | ```js
|
149 | var p = new Promise(function (resolve, reject) {
|
150 | reject(new Error('foo'));
|
151 | });
|
152 | p.then(..., lazyAssync);
|
153 | // Uncaught Error: foo
|
154 | ```
|
155 |
|
156 | ## Predicate function as a condition
|
157 |
|
158 | Typically, JavaScript evaluates the condition expression first, then calls *lazyAss*.
|
159 | This means the function itself sees only the true / false result, and not the expression
|
160 | itself. This makes makes the error messages cryptic
|
161 |
|
162 | lazyAss(2 + 2 === 5);
|
163 | // Error
|
164 |
|
165 | We usually get around this by giving at least one additional message argument to
|
166 | explain the condition tested
|
167 |
|
168 | lazyAss(2 + 2 === 5, 'addition')
|
169 | // Error: addition
|
170 |
|
171 | *lazyAss* has a better solution: if you give a function that evaluates the condition
|
172 | expression, if the function returns false, the error message will include the source
|
173 | of the function, making the extra arguments unnecessary
|
174 |
|
175 | lazyAss(function () { return 2 + 2 === 5; });
|
176 | // Error: function () { return 2 + 2 === 5; }
|
177 |
|
178 | The condition function has access to any variables in the scope, making it extremely
|
179 | powerful
|
180 |
|
181 | var foo = 2, bar = 2;
|
182 | lazyAss(function () { return foo + bar === 5; });
|
183 | // Error: function () { return foo + bar === 5; }
|
184 |
|
185 | In practical terms, I recommend using separate predicates function and
|
186 | passing relevant values to the *lazyAss* function. Remember, there is no performance
|
187 | penalty!
|
188 |
|
189 | var foo = 2, bar = 2;
|
190 | function isValidPair() {
|
191 | return foo + bar === 5;
|
192 | }
|
193 | lazyAss(isValidPair, 'foo', foo, 'bar', bar);
|
194 | // Error: function isValidPair() {
|
195 | // return foo + bar === 5;
|
196 | // } foo 2 bar 2
|
197 |
|
198 | ## Testing
|
199 |
|
200 | This library is fully tested under Node and inside browser environment (CasperJs).
|
201 | I described how one can test asynchronous assertion throwing in your own projects
|
202 | using Jasmine in [a blog post](http://glebbahmutov.com/blog/testing-async-lazy-assertion/).
|
203 |
|
204 | ## TypeScript
|
205 |
|
206 | If you use this function from a TypeScript project, we provide ambient type
|
207 | definition file. Because this is CommonJS library, use it like this
|
208 |
|
209 | ```ts
|
210 | import la = require('lazy-ass')
|
211 | // la should have type signature
|
212 | ```
|
213 |
|
214 | ### Small print
|
215 |
|
216 | Author: Gleb Bahmutov © 2014
|
217 |
|
218 | * [@bahmutov](https://twitter.com/bahmutov)
|
219 | * [glebbahmutov.com](http://glebbahmutov.com)
|
220 | * [blog](http://glebbahmutov.com/blog)
|
221 |
|
222 | License: MIT - do anything with the code, but don't blame me if it does not work.
|
223 |
|
224 | Spread the word: tweet, star on github, etc.
|
225 |
|
226 | Support: if you find any problems with this module, email / tweet /
|
227 | [open issue](https://github.com/bahmutov/lazy-ass/issues) on Github
|
228 |
|
229 | ## MIT License
|
230 |
|
231 | Copyright (c) 2014 Gleb Bahmutov
|
232 |
|
233 | Permission is hereby granted, free of charge, to any person
|
234 | obtaining a copy of this software and associated documentation
|
235 | files (the "Software"), to deal in the Software without
|
236 | restriction, including without limitation the rights to use,
|
237 | copy, modify, merge, publish, distribute, sublicense, and/or sell
|
238 | copies of the Software, and to permit persons to whom the
|
239 | Software is furnished to do so, subject to the following
|
240 | conditions:
|
241 |
|
242 | The above copyright notice and this permission notice shall be
|
243 | included in all copies or substantial portions of the Software.
|
244 |
|
245 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
246 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
247 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
248 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
249 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
250 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
251 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
252 | OTHER DEALINGS IN THE SOFTWARE.
|
253 |
|
254 | [lazy-ass-icon]: https://nodei.co/npm/lazy-ass.svg?downloads=true
|
255 | [lazy-ass-url]: https://npmjs.org/package/lazy-ass
|
256 | [lazy-ass-ci-image]: https://travis-ci.org/bahmutov/lazy-ass.svg?branch=master
|
257 | [lazy-ass-ci-url]: https://travis-ci.org/bahmutov/lazy-ass
|
258 | [lazy-ass-coverage-image]: https://coveralls.io/repos/bahmutov/lazy-ass/badge.svg
|
259 | [lazy-ass-coverage-url]: https://coveralls.io/r/bahmutov/lazy-ass
|
260 | [lazy-ass-code-climate-image]: https://codeclimate.com/github/bahmutov/lazy-ass/badges/gpa.svg
|
261 | [lazy-ass-code-climate-url]: https://codeclimate.com/github/bahmutov/lazy-ass
|
262 | [lazy-ass-codacy-image]: https://www.codacy.com/project/badge/b60a0810c9af4fe4b2ae685932dbbdb8
|
263 | [lazy-ass-codacy-url]: https://www.codacy.com/public/bahmutov/lazy-ass.git
|
264 | [lazy-ass-dependencies-image]: https://david-dm.org/bahmutov/lazy-ass.svg
|
265 | [lazy-ass-dependencies-url]: https://david-dm.org/bahmutov/lazy-ass
|
266 | [lazy-ass-devdependencies-image]: https://david-dm.org/bahmutov/lazy-ass/dev-status.svg
|
267 | [lazy-ass-devdependencies-url]: https://david-dm.org/bahmutov/lazy-ass#info=devDependencies
|
268 | [semantic-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
|
269 | [semantic-url]: https://github.com/semantic-release/semantic-release
|