0

I'm writing an API in JavaScript, and I'm confused whether or not I should check if the API user is passing me the correct type on which the API expects to work. Actually not the type, but if you are passing me the object with the properties on which API will work. Is it a good practice for me to issue an error if the user does not pass an object with the properties expected for the API, or should I leave this on behalf of the JavaScript compiler?

I should check the type like this:

function PackageManager() {
    this.packages = {};
}

PackageManager.prototype.addPackage = function(assetPackage) {
    Array.isArray(assetPackage) ? this.addAssetPackages(assetPackage) : this.addAssetPackage(assetPackage);
}

PackageManager.prototype.addAssetPackages = function(assetPackages) {
     for(let i = 0; i < assetPackages.length; i++) 
         this.addAssetPackage(assetPackages[i]);
}

PackageManager.prototype.addAssetPackage = function(assetPackage) {
    if (!this.isAssetPackage(assetPackage))
        throw new TypeError('You are passing the argument "'+ assetPackage +'" and the addPackage function expects an AssetPackage.');
    this.packages[assetPackage.name] = {
        name: assetPackage.name,
        assets: assetPackage.assets
    };
}

PackageManager.prototype.isAssetPackage = function(val) {
    return (this.isObject(val) && val.name && val.assets ? true : false);
}

PackageManager.prototype.isObject = function(val) {
    return (typeof val === "object" && val !== null ? true : false);
}
export default PackageManager;

or simply let the API user handle the compiler:

function PackageManager() {
    this.packages = {};
}

PackageManager.prototype.addPackage = function(assetPackage) {
     Array.isArray(assetPackage) ? this.addPackages(assetPackage) : addAssetPackage(assetPackage);
}

PackageManager.prototype.addPackages = function(assetPackages) {
     for(let i = 0; i < assetPackages.length; i++)
         addAssetPackage(assetPackages[i]);
}

PackageManager.prototype.addAssetPackage = function(assetPackage) {
    this.packages[assetPackage.name] = {
        name: assetPackage.name,
        assets: assetPackage.assets
    };
}

export default PackageManager;

how do you handle this case in your JavaScript APIs?

  • What do you mean by "let the user handle the compiler?" – Robert Harvey Oct 5 '18 at 23:48
  • 1
    It's called "data validation," and yes, you should do it. – Robert Harvey Oct 5 '18 at 23:48
  • @RobertHarvey "...handle the compiler" = Let it parse the errors that the compiler gives when it passes an object that the API can not work with. – PerduGames Oct 5 '18 at 23:58
  • Javascript in the browser is a notoriously opaque programming language when it comes to error handling. Sometimes you'll get an error that makes sense, sometimes you won't. Sometimes you won't get an error at all; it will simply not work or produce the wrong result. Do you think it's better to leave such a language to its own devices, or provide a meaningful error message so that you don't frustrate the caller? – Robert Harvey Oct 6 '18 at 0:08
  • @RobertHarvey I was told something that made me think, "JavaScript is a dynamic language, and what you are doing is wanting to use it as a typed language," and I thought a little about it and the question came to me, because maybe I should not do that type of verification, after all dynamic languages ​​give us the Duck Typed, because anyway if it passes an object that does not contain "assets", it will receive an error from the compiler soon about this, and if it does not provide a property "name" will have an "undefined" property in packages, which also probably will appear the error soon. – PerduGames Oct 6 '18 at 0:10
0

Being that JavaScript is a dynamically typed language, you can assume the users of that language know it is dynamically-typed. If you expect a certain kind of object, document it either in the how-to guides for your API, or using something like JsDoc.

The constant checking of data types at runtime is replicating what a compiler does. If you need to verify this, you need a strongly typed language. If your target platform does not support a strongly typed language, then you don't need to check data types.

  1. Document what you expect
  2. Add error handling where it makes business sense
  3. Do not add error handling if it would normally be verified by a compiler in a strongly typed language
  4. Assume your users know they are not using a strongly typed language
  5. Tell them to RTFM when something wonky goes wrong

Oh, and make sure the "F" in RTFM means "Friendly" and "well-written."

There is 1 exception, however. And this exception is perfectly illustrated in your PackageManager#addPackage method. If you want to support method overloading in a language that does not support method overloading, you absolutely must check data types.

  • "Do not add error handling if it would normally be verified by a compiler in a strongly typed language" - this is a very good tip, I will always look at it and think now. – PerduGames Oct 6 '18 at 9:56
  • 2
    I don't agree with your second paragraph. The basis for checking types yourself is not "would a strongly-typed language do it for you," because clearly a dynamically-typed language does not. The basis should be "do you need this?" – Robert Harvey Oct 6 '18 at 17:45
  • @RobertHarvey: Since JavaScript is a dynamically typed language, the issue of "do you need [to check types]" is moot. The language doesn't support it. If you really need it, JavaScript isn't the right tool for the job. TypeScript would be an alternative, but that doesn't stop code originally written in JavaScript from calling those functions or methods that were transpiled from TypeScript to JavaScript. I feel like front end developers need to complain more about this to browser vendors and the W3C. We need a strongly typed language too. – Greg Burghardt Oct 7 '18 at 0:36
0

In this particular example code, the only required field to not blow up is name, because it gets used in

this.packages[assetPackage.name] = ...

The other field, assets, could be null (or missing) and the code would run fine. Now, perhaps, you want to ensure that assets also exists.

How I would document this

  1. Document that you expect objects with a required name field,
  2. If desired, document that other fields, like assets and foobar, are required.

How I would "validate"

  1. Test for the required fields, but don't bother with typeof val === "object" because that is, well, pretty pointless here. You would make that test if you want to handle a String or a Number differently, as part of function overloading, but that is not the case here.
  2. Just test if (!val.name) throw... (if you want, also test val.assets, etc...) (purists note: this also prohibits empty names, YMMV, so you might preferif (val.name == null)
  3. Consider user friendly defaults for values (other than name). For example, if assets is an object, and you don't want nulls later on, use assets: assetPackage.assets || {}

p.s. If your software is security related, controls a pacemaker, etc., you need more valudation!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.