DEV Community

Besworks
Besworks

Posted on • Edited on

Parameter Type Safety in JavaScript

Today I got bit by a small but annoying bug due to the lack of type safety in javascript. I scrapped an experimental change to a method but forgot to remove the new parameter from the function declaration.

When I finally tracked it down I decided to solve this problem once and for all, without resorting to compiling TypeScript. I'm not a fan of their syntactic sugar. I just wanted a clean, simple way to check parameters in a declarative inline fashion, and I didn't like the look any libraries that I could find which claimed to handle this sort of thing. So I got copilot to help me write and test my own, which I feel is elegant and intuitive.

It Easy!

import { ValidatedMethod } from './validated-method.js';

class UserService {
    createUser = new ValidatedMethod({
        username: 'string',
        age: 'number',
        active: 'boolean',
        roles: 'array',
        settings: 'object',
        email: /^[^@]+@[^@]+\.[^@]+$/,
        birthday: ['string','optional'],
        title: 'string'
    }, async (opts) => {
        // Parameters are validated, safe to use
        return await db.users.create(opts);
    });
}

const myService = new UserService();

myService.createUser({
    username: 'besworks',
    age: 40,
    active: true,
    roles: [ 'admin', 'wizard' ],
    settings: { darkmode: 'auto' },
    email: 'example@domain.tld'
    // birthday: is optional so undefined is ok
    // Throw TypeError because title: is undefined
});
Enter fullscreen mode Exit fullscreen mode

You can choose to handle the errors with try/catch at runtime or use this as a development tool only to detect type errors early and account for them. This saves a ton of boilerplate type checking inside every function. It's still good practice to do additional checking of your values in a production environment, but at least you can safely call something like roles.length and know for certain that you will be checking the length of an array and not the number of characters in a string.


It's Flexibile!

Using named parameters lets them be order-independent allowing more flexibility when making changes to your methods later on, but you can use it with single or positional parameters for convenience. Optionally you can also specify an expected return type and throw an error if your function fails to meet the requirements.

const $ = new ValidatedMethod('string', (query) => {
    const result = document.querySelectorAll(query);
    console.log('selecting:', query, result);
    return result;
}, NodeList);
Enter fullscreen mode Exit fullscreen mode

Wrap it around any function to make to make it safe and explicitly report what types are involved.

const add = new ValidatedMethod(
    ['number', 'number'], (a, b) => a + b, 'number'
);

add(40,2);
// returns 42

add('test', 2);
// TypeError: expected number got string
Enter fullscreen mode Exit fullscreen mode

We passed the wrong type but instead of the js engine concatenating the result and tripping up our next operation we get the opportunity to fix the issue right away.


Use strict type checking when required.

const getValue = new ValidatedMethod(
    'string', a => {
        let b = parseInt(a);
        return a;
    }, 'strictint'
);

getValue('42');
// TypeError: Return value 42 is not type strictint
Enter fullscreen mode Exit fullscreen mode

We returned the wrong var but we find out right away instead of dealing with the effects in the next code block


It's Tested!

✓ Basic types test passed
✓ Strict types test passed
✓ Optional and Any types test passed
✓ Number types test passed
✓ Integer rounding test passed
✓ Unexpected parameters test passed
✓ Quiet mode test passed
✓ Custom types test passed
✓ Type coercion test passed
✓ Error cases test passed
✓ Function parameter test passed
✓ Multiple types and optional aliases test passed
✓ Null value handling test passed
✓ Regex validation test passed
✓ Single parameter test passed
✓ Single custom type parameter test passed
✓ Array arguments test passed
✓ Async method test passed
✓ Async stress test passed
✓ Promise chain test passed
✓ Concurrent async test passed
✓ Async timeout test passed
✓ Promise rejection test passed
✓ Return type validation test passed
✓ Return type edge cases test passed
✓ Zero parameter function test passed
✓ Custom return validator test passed
✓ Async return type validation test passed
✓ Custom validator function test passed
✓ Validator edge cases test passed
✓ Helper function (_$) test passed
Enter fullscreen mode Exit fullscreen mode

It's Evolving!

I think I've covered all the edge cases imaginable, but if anyone runs into an unsupported usage that they feel warrants attention, just let me know and I'll look into it.

Top comments (0)