UNPKG

8.97 kBMarkdownView Raw
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
17Note: only tested against Node 4+
18
19## Example
20
21Regular assertions evaluate all arguments and concatenate message
22EVERY time, even if the condition is true.
23
24```js
25console.assert(typeof foo === 'object',
26 'expected ' + JSON.stringify(foo, null, 2) + ' to be an object');
27```
28
29Lazy assertion function evaluates its arguments and forms
30a message ONLY IF the condition is false
31
32```js
33const {lazyAss} = require('lazy-ass')
34lazyAss(typeof foo === 'object', 'expected', foo, 'to be an object');
35// shorter version
36const {lazyAss: la} = require('lazy-ass')
37la(typeof foo === 'object', 'expected', foo, 'to be an object');
38```
39
40Concatenates strings, stringifies objects, calls functions - only if
41condition is false.
42
43```js
44function environment() {
45 // returns string
46}
47var user = {} // an object
48lazyAsync(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)
57than 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)
60than passing 2 separate strings.
61
62## Install
63
64Node: `npm install lazy-ass --save` then `var la = require('lazy-ass');`.
65You can attach the methods to the global object using
66`require('lazy-ass').globalRegister();`.
67
68Browser: `bower install lazy-ass --save`, include `index.js`,
69attaches functions `lazyAss` and `la` to `window` object.
70
71## Notes
72
73You can pass as many arguments to *lazyAss* after the condition. The condition
74will be evaluated every time (this is required for any assertion). The rest of arguments
75will 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
81There will be single space between the individual parts.
82
83## Lazy async assertions
84
85Sometimes you do not want to throw an error synchronously, breaking the entire
86execution stack. Instead you can throw an error asynchronously using `lazyAssync`,
87which internally implements logic like this:
88
89```js
90if (!condition) {
91 setTimeout(function () {
92 throw new Error('Conditions is false!');
93 }, 0);
94}
95```
96
97This allows the execution to continue, while your global error handler (like
98my favorite [Sentry](http://glebbahmutov.com/blog/know-unknown-unknowns-with-sentry/))
99can still forward the error with all specified information to your server.
100
101```js
102lazyAss.async(false, 'foo');
103console.log('after assync');
104// output
105after assync
106Uncaught Error: foo
107```
108
109In this case, there is no meaningful error stack, so use good message
110arguments - there is no performance penalty!
111
112## Rethrowing errors
113
114If the condition itself is an instance of Error, it is simply rethrown (synchronously or
115asynchronously).
116
117```js
118lazyAss(new Error('foo'));
119// Uncaught Error: foo
120```
121
122Useful to make sure errors in the promise chains are
123[not silently ignored](https://glebbahmutov.com/blog/why-promises-need-to-be-done/).
124
125For example, a rejected promise below this will be ignored.
126
127```js
128var p = new Promise(function (resolve, reject) {
129 reject(new Error('foo'));
130});
131p.then(...);
132```
133
134We can catch it and rethrow it *synchronously*, but it will be ignored too (same way,
135only one step further)
136
137```js
138var p = new Promise(function (resolve, reject) {
139 reject(new Error('foo'));
140});
141p.then(..., lazyAss);
142```
143
144But we can actually trigger global error if we rethrow the error *asynchronously*
145
146```js
147var p = new Promise(function (resolve, reject) {
148 reject(new Error('foo'));
149});
150p.then(..., lazyAssync);
151// Uncaught Error: foo
152```
153
154## Predicate function as a condition
155
156Typically, JavaScript evaluates the condition expression first, then calls *lazyAss*.
157This means the function itself sees only the true / false result, and not the expression
158itself. This makes makes the error messages cryptic
159
160 lazyAss(2 + 2 === 5);
161 // Error
162
163We usually get around this by giving at least one additional message argument to
164explain 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
170expression, if the function returns false, the error message will include the source
171of the function, making the extra arguments unnecessary
172
173 lazyAss(function () { return 2 + 2 === 5; });
174 // Error: function () { return 2 + 2 === 5; }
175
176The condition function has access to any variables in the scope, making it extremely
177powerful
178
179 var foo = 2, bar = 2;
180 lazyAss(function () { return foo + bar === 5; });
181 // Error: function () { return foo + bar === 5; }
182
183In practical terms, I recommend using separate predicates function and
184passing relevant values to the *lazyAss* function. Remember, there is no performance
185penalty!
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
198This library is fully tested under Node and inside browser environment (CasperJs).
199I described how one can test asynchronous assertion throwing in your own projects
200using Jasmine in [a blog post](http://glebbahmutov.com/blog/testing-async-lazy-assertion/).
201
202## TypeScript
203
204If you use this function from a TypeScript project, we provide ambient type
205definition file. Because this is CommonJS library, use it like this
206
207```ts
208import la = require('lazy-ass')
209// la should have type signature
210```
211
212### Small print
213
214Author: 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
223License: MIT - do anything with the code, but don't blame me if it does not work.
224
225Spread the word: tweet, star on github, etc.
226
227Support: 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
232Copyright (c) 2014 Gleb Bahmutov
233
234Permission is hereby granted, free of charge, to any person
235obtaining a copy of this software and associated documentation
236files (the "Software"), to deal in the Software without
237restriction, including without limitation the rights to use,
238copy, modify, merge, publish, distribute, sublicense, and/or sell
239copies of the Software, and to permit persons to whom the
240Software is furnished to do so, subject to the following
241conditions:
242
243The above copyright notice and this permission notice shall be
244included in all copies or substantial portions of the Software.
245
246THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
247EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
248OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
249NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
250HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
251WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
252FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
253OTHER 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