Neophobe Plebeian Mumpsimus

    nothing-mock
    TypeScript icon, indicating that this package has built-in type declarations

    1.0.4 • Public • Published

    Nothing

    npm package npm package

    Nothing is a chainable, callable mock object which always returns itself. You can use it instead of null and undefined values so you don't have to place safety checks all over your code. The implementation uses Symbol and Proxy behind the hood which are widely supported by modern desktop and mobile browsers and can be used without a polyfill.

    How to install

    npm i -S nothing-mock
    

    How to use

    A simple example

    import {Nothing} from 'nothing-mock'
    
    const foo = Nothing
    foo.bar.baz.qux().spam.ham[0].map((x) => x + 1).eggs.someFn() // returns Nothing

    Reducing boilerplate

    import {Nothing} from 'nothing-mock'
    
    // A regular function with null-checks
    function someFunctionWithNullChecks(foo) {
    	return foo &&
    		foo.bar &&
    		foo.bar.baz &&
    		foo.bar.baz.qux &&
    		foo.bar.baz.qux()
    }
    
    // There is no need to check for null/undefined if you use Nothing
    function someFunction(foo) {
    	return foo.bar.baz.qux()
    }
    
    someFunctionWithNullChecks(null) // returns null
    someFunction(Nothing) // returns Nothing
    someFunction(null) // throws an exception

    JSON serialization/deserialization

    import {Nothing, deserialize, serialize} from 'nothing-mock'
    
    const json = `{
    	"posts": [{
    		"id": 1,
    		"userId": 12,
    		"content": "post 1",
    		"comments": [{
    			"id": 1,
    			"userId": 34,
    			"content": "comment 1"
    		}, {
    			"id": 2,
    			"userId": 56,
    			"content": "comment 2"
    		}]
    	}, {
    		"id": 2,
    		"userId": 78,
    		"content": "post 2",
    		"comments": null
    	}]
    }`
    
    const {posts} = deserialize(json) /* returns: [{
    	"id": 1,
    	"userId": 12,
    	"content": "post 1",
    	"comments": [{
    		"id": 1,
    		"userId": 34,
    		"content": "comment 1"
    	}, {
    		"id": 2,
    		"userId": 56,
    		"content": "comment 2"
    	}]
    }, {
    	"id": 2,
    	"userId": 78,
    	"content": "post 2",
    	"comments": Nothing // null values are replaced with Nothing
    }] */
    
    function renderPostWithComments(post) {
    	return `<div>
    		<p>${post.content}</p>
    		<ul>${post.comments.map((comment) =>
    			`<li>${comment.content}</li>`).join('')
    		}</ul>
    	</div>`
    }
    
    posts.map(renderPostWithComments).join('') /* returns:
    `<div>
    	<p>post 1</p>
    	<ul>
    		<li>comment 1</li>
    		<li>comment 2</li>
    	</ul>
    </div>
    <div>
    	<p>post 2</p>
    	<ul></ul> // Nothing is rendered empty
    </div>` */
    
    // Serializes an object to JSON and
    // replaces all Nothing values with null
    serialize({posts})

    Helper functions

    import {Nothing, toBool, isNothing, isSomething} from 'nothing-mock'
    
    const list = [Nothing, true, false, null, undefined, 0, 1, NaN, '', {}, []]
    list.filter(toBool) // [true, 1, {}, []]
    list.filter(isNothing) // [Nothing]
    list.filter(isSomething) // [true, false, 0, 1, NaN, "", {}, []]

    Properties which don't return Nothing

    import {Nothing} from 'nothing-mock'
    
    Nothing.length // 0
    Nothing.name // a string
    Nothing.prototype // an object with a constructor
    Nothing.toLocaleString() // ""
    Nothing.toString() // ""
    Nothing.valueOf() // false

    Gotchas

    import {Nothing, toBool} from 'nothing-mock'
    
    String(Nothing) // ""
    Nothing.toString() // ""
    Nothing + 'a string' // "a string"
    Nothing * 123 // 0
    Nothing - 123 // -123
    
    // Gotcha: concatenation of Nothing and a number returns a string
    Nothing + 123 // "123"
    
    // Solution: Nothing can be excplicitly converted to a number
    Number(Nothing) // 0
    Number(Nothing) + 123 // 123
    
    // Gotcha: typecasting of Nothing to a boolean returns true
    Boolean(Nothing) // true
    !!Nothing // true
    
    // Solution: Nothing can be converted to false
    Nothing.valueOf() // false
    toBool(Nothing) // false
    
    // Gotcha: returning Nothing from a promise never
    // resolves as Nothing is a thenable object
    somePromise
    	.then(() => Nothing)
    	.then((result) => result) // pending indefinitely
    
    // Solution: wrapping Nothing resolves the issue
    somePromise
    	.then(() => ({result: Nothing}))
    	.then((result) => result) // promise resolves

    FAQ

    Q-1: Proxies are slow and there is a runtime overhead. Why should I use Nothing?

    A: You should keep a few things in mind:

    1. "Premature optimization is the root of all evil" - Donald E. Knuth.
    2. Have you checked the performance of Nothing? Does it really impact the performance of your code? If it does, you can always opt out using Nothing for performance-critical parts of your code.
    3. You can use Nothing for writing unit tests which are less likely to be performance-dependant.

    Q-2: I believe that it's hard to understand the logic as the code will fail silently if I would use Nothing. I prefer to use try/catch blocks instead, e.g.:

    try {
    	foo.bar.baz()
    } catch (e) {
    	// deal with it somehow
    }

    A: Many functional programming languages either don't have or don't endorse the use of imperative constructs such as try/catch blocks because they introduce so-called side effects which actually make it harder to debug and reason about the code. And programs which are written in functional programming languages are considered to be less error-prone and easier to support.

    You can always check the result if a function call should never return Nothing and then handle it properly:

    const someFunction = (handleNothing, arg) => {
    	const result = foo.bar.baz(arg)
    	return isNothing(result) ? handleNothing(arg) : result
    }

    Q-3: Why should I use Nothing if there are better alternatives like optional chaining or lodash.get?

    A: Each of these solutions have their pros and cons. Your choice should depend on the use-case:

    1. Optional chaining syntax would be the best choice, but it requires a transpilation step as modern browsers don't support the syntax and it might take a while before it will get into the future ECMAScript standard.
    2. lodash.get is good for a basic property chain traversal, but it requires an alien syntax and fails when there is a need to call a method somewhere in a property chain:
    import get from 'lodash.get'
    
    var foo = null
    get(foo, ['bar', 'baz'])() // this will throw an exception
    
    var baz = get(foo, ['bar', 'baz'])
    baz && baz() // this won't work if `baz` should be bound to the context of `bar`
    
    // For example:
    var foo = {
    	bar: {
    		baz() {
    			console.log(this.qux)
    		},
    		qux: 'hello'
    	}
    }
    
    foo.bar.baz() // "hello"
    get(foo, ['bar', 'baz'])() // undefined
    
    // This would be a proper solution:
    var bar = get(foo, ['bar'])
    var baz = get(bar, ['baz'])
    baz && baz.call(bar) // "hello"
    
    // But then it's easier to get back to the regular syntax:
    foo && foo.bar && foo.bar.baz && foo.bar.baz()
    
    // And good luck using `get` for something like this:
    foo.bar.baz()[0].map(() => { /* do something */ })
    
    // BTW, an implementation of a lodash-like `get` helper-function is basically a one-liner:
    const get = (o, a) => a.reduce((p, c) => p && p[c], o)

    Q-4: I am still not convinced and ain't gonna use Nothing!

    A: Thanks for letting me know! Seriously, it's your choice, I am down with it.

    License

    MIT

    Install

    npm i nothing-mock

    DownloadsWeekly Downloads

    4,353

    Version

    1.0.4

    License

    MIT

    Unpacked Size

    18.2 kB

    Total Files

    13

    Last publish

    Collaborators

    • slmgc