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