This content originally appeared on DEV Community and was authored by Jordan Brennan
Months ago I wrote The reasons I don't use Typescript and one reason more than others struck a nerve with some readers. I said developers "can embrace the fault-tolerant nature of the web and wisely use JavaScript's strict type enforcement when actually necessary."
Many readers drove by, rolled down their window, and yelled, "Fault tolerance sucks!" and then sped off when asked why. Others denied that JavaScript even has strict typing abilities and accused me of "spreading misinformation".
So, I'm writing this to explain my reason in more detail and to document the very long and very useful list of tools already in JavaScript that not only help you verify types, but help you further harden your code at runtime.
Is fault tolerance good?
(I wanted to share a link to an old blog post on Los Techies - I think - written by Addy Osmani - I think - that introduced me to this topic, but I can't find it. If you know it, please share!)
A system is said to be fault-tolerant if when a component of the system fails the system continues to function.
Note how the plane is still up in the air. This is a good thing.
This is what fault-tolerance does not look like:
An issue inside a component inside a program inside an operating system caused the whole operating system to just give up. If that wasn't a bad enough design, the system then forces you to sit and wait while it rubs it in your face - there’s still 75% more failure to go!
In the spirit of that old GM vs. Microsoft joke, what if cars were built this way? Your car would suddenly shut down because one of its four tires didn't have the exact tire pressure dictated by the manufacturer's specifications? This would be a terrible driving experience and kind of dangerous.
So yes, fault tolerance in systems is good!
A fault tolerant web
Thankfully, early engineers designed web platform technologies - HTML, CSS, JavaScript - and the browsers that implement them to be more airplane and car and less Windows.
For example, what will happen when this document is loaded by your browser:
<!DOCTYPE html>
<html>
<body>
<h1>Hello, world!
</body>
</html>
It will display "Hello, world!" despite the missing closing tag.
What about this document?
<!DOCTYPE HTML><title>Hello</title><p>Welcome to this example</p>
That works too. In fact, it's an example straight from the optional tags spec.
What about this?
<!DOCTYPE HTML><title>Hello</title><asdfghjkl>Hello, world!
Does this mean we should omit tags or not bother closing tags or write nonsense? Of course not, but it would be a real shame if the user was left staring at a blank page because the browser crashed on a missing or unknown tag.
The web platform has been designed to be fault-tolerant. It values producing something for the user over requiring everything at all times to be absolutely perfectly correct in order to function. Kind of like how an airplane is designed to resist gravity as much as possible, even in unexpected circumstances.
Here's another example with CSS:
div {
display: grid;
}
The code is modern and technically perfect, but older browsers won't know what grid
is and yet they will dutifully carry on with their CSS calculations and paint content as best they can without giving up and crashing. Even this won't crash a browser:
div {
banana: split;
}
Again, the point is not that the web platform tolerates sloppiness, but rather should something imperfect or unsupported slip through your quality controls, it won't completely ruin your users' experience.
When we create our systems - our apps - we choose to embrace or reject the nature of the web by conscientiously allowing a little wiggle room or by attempting a level of rigidity that might shatter in the face of something unknown.
There's a sweet spot between careless and "can't accept JavaScript, must use TypeScript."
JavaScript allows things to happen that some languages would not allow, which seems to rub a lot of TypeScript fans the wrong way. I believe they are used to working with languages and in environments that are more predictable and controllable, like a having a highly-programmed autonomous car confined to known roads. Web apps, on the other hand, have a human driver who's late for work trying to take the fastest route. Web apps simply demand a more tolerant runtime.
When it is time to build in some rigidity - even absolute correctness - there are many tools available natively just waiting for you to use them.
Leverage JavaScript's tools when necessary
JavaScript includes lots of features and tools that increase the strictness and ensure correctness of your code, including type checking.
Here they all are (I think I got them all), each with a link to MDN and a brief explanation for their use case:
Type checking
These are useful for enforcing the type of an object or comparing the types of objects.
The typeof operator returns a string indicating the type of the unevaluated operand.
It's not perfect, but it enables type checking for string
, number
, bigint
, boolean
, function
, symbol
, object
, and undefined
.
Object.prototype.toString.call(obj)
Every object has a
toString()
method...toString()
returns "[object type]", wheretype
is the object type.
This method can check object types like Array
, Date
, RegEx
, and more. This is best wrapped up in a little helper function.
The
instanceof
operator tests to see if theprototype
property of a constructor appears anywhere in the prototype chain of an object.
There's also a more verbose, but self-explanatory way of checking: Object.getPrototypeOf(obj) === MyClass.prototype
. Arrays have a gotcha, see next.
The
Array.isArray()
method determines whether the passed value is anArray
.
There are edge cases that make using this method safer than instanceof
.
The
Number.isInteger()
method determines whether the passed value is an integer.
There are edge cases to be aware of as well as Number.isSafeInteger()
.
The strict equality operator checks whether its two operands are equal...[it] always considers operands of different types to be different.
Skips the type coercion for a more accurate comparison.
Object integrity
These are useful for ensuring that what you are accessing is what you expect it to be.
The value of a constant can't be changed through reassignment, and it can't be redeclared.
Variables declared with var
and let
can potentially be reassigned a value your code can't handle, so using const
helps protect against this.
The optional chaining operator permits reading the value of a property located deep within a chain of connected objects without having to expressly validate that each reference in the chain is valid.
Optional chaining in my opinion is the greatest addition to JavaScript since ServiceWorker. It's our most powerful tool to fight Uncaught TypeError: Cannot read property
, which has been identified by Rollbar as the number one production JavaScript error see Top 10 JavaScript Errors From 1,000 Projects.
Data shapes in web apps can be unpredictable due to the fact that most data originates somewhere outside the app (e.g. your own services, 3rd-party services, hosted files and objects, and more). Even well-defined custom types can fail to account for all of an object properties, so TypeScript or no TypeScript, you should use this on data your code didn't originate.
The
hasOwnProperty()
method returns a boolean indicating whether the object has the specified property as its own property (as opposed to inheriting it).
When you need to verify that a property exists directly on an object, use this. Use in
only when you know that checking the object and its prototype chain is acceptable.
The nullish coalescing operator is a logical operator that returns its right-hand side operand when its left-hand side operand is
null
orundefined
, and otherwise returns its left-hand side operand.
This is needed when you can't allow the normal falsey rules because a valid value might be rejected, i.e. when you do need to accept 0
(zero), ''
(empty string), or false
values.
Pair it with assignment to ensure nullish values are replaced with something valid, e.g. foo ??= something
.
The
Object.is()
method determines whether two values are the same value.
Its rules for equality are slightly different than ===
and ==
.
The
Object.seal()
method seals an object, preventing new properties from being added to it...Values of present properties can still be changed as long as they are writable.
This is like const
on steroids. The shape of the object cannot change - you can't add or remove properties - but you can edit their values.
The
Object.freeze()
method freezes an object. A frozen object can no longer be changed [in any way].
Like seal()
, but you can't even edit existing properties. Frozen means nothing about that object can be changed, but one thing to remember is an object's "values that are objects can still be modified, unless they are also frozen."
Fault tolerance is still not enough
Whether you're writing TypeScript or not, those 15 tools should be used often, but in the end it still won't be enough. After types have been checked and objects prove they have the properties we expect them to have, there's still a void in this problem space. That void is validation. No, not input validation for security purposes - you do that server-side - but rather an extension of the validation we're already doing, i.e. ensuring what we have been given meets our requirements before operating on it.
I suggest you read Adam's Tossing TypeScript for a detailed explanation on this and then try his allow
lib.
Are these validations always needed? No. There are advantages to JavaScript's forgiving nature and allowing for it with eyes wide open can result in more simple and resilient code that might just save your users from an unnecessary bug.
This content originally appeared on DEV Community and was authored by Jordan Brennan
Jordan Brennan | Sciencx (2021-07-20T01:19:10+00:00) Fault tolerance on the web. Retrieved from https://www.scien.cx/2021/07/20/fault-tolerance-on-the-web/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.