|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841 |
- <!doctype html><!-- This is a valid HTML5 document. -->
- <!-- Screen readers, SEO, extensions and so on. -->
- <html lang=fr>
- <!-- Has to be within the first 1024 bytes, hence before the <title>
- See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
- <meta charset=utf-8>
- <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
- <!-- The viewport meta is quite crowded and we are responsible for that.
- See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
- <meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
- <!-- Required to make a valid HTML5 document. -->
- <title>Robust Client-Side JavaScript (archive) — David Larlet</title>
- <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
- <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons/apple-touch-icon.png">
- <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons/favicon-32x32.png">
- <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons/favicon-16x16.png">
- <link rel="manifest" href="/manifest.json">
- <link rel="mask-icon" href="/static/david/icons/safari-pinned-tab.svg" color="#5bbad5">
- <link rel="shortcut icon" href="/static/david/icons/favicon.ico">
- <meta name="apple-mobile-web-app-title" content="David Larlet">
- <meta name="application-name" content="David Larlet">
- <meta name="msapplication-TileColor" content="#da532c">
- <meta name="msapplication-config" content="/static/david/icons/browserconfig.xml">
- <meta name="theme-color" content="#f0f0ea">
- <!-- That good ol' feed, subscribe :p. -->
- <link rel=alternate type="application/atom+xml" title=Feed href="/david/log/">
-
- <meta name="robots" content="noindex, nofollow">
- <meta content="origin-when-cross-origin" name="referrer">
- <!-- Canonical URL for SEO purposes -->
- <link rel="canonical" href="https://molily.de/robust-javascript/">
-
- <style>
- /* http://meyerweb.com/eric/tools/css/reset/ */
- html, body, div, span,
- h1, h2, h3, h4, h5, h6, p, blockquote, pre,
- a, abbr, address, big, cite, code,
- del, dfn, em, img, ins,
- small, strike, strong, tt, var,
- dl, dt, dd, ol, ul, li,
- fieldset, form, label, legend,
- table, caption, tbody, tfoot, thead, tr, th, td,
- article, aside, canvas, details, embed,
- figure, figcaption, footer, header, hgroup,
- menu, nav, output, ruby, section, summary,
- time, mark, audio, video {
- margin: 0;
- padding: 0;
- border: 0;
- font-size: 100%;
- font: inherit;
- vertical-align: baseline;
- }
- /* HTML5 display-role reset for older browsers */
- article, aside, details, figcaption, figure,
- footer, header, hgroup, menu, nav, section { display: block; }
- body { line-height: 1; }
- blockquote, q { quotes: none; }
- blockquote:before, blockquote:after,
- q:before, q:after {
- content: '';
- content: none;
- }
- table {
- border-collapse: collapse;
- border-spacing: 0;
- }
-
- /* http://practicaltypography.com/equity.html */
- /* https://calendar.perfplanet.com/2016/no-font-face-bulletproof-syntax/ */
- /* https://www.filamentgroup.com/lab/js-web-fonts.html */
- @font-face {
- font-family: 'EquityTextB';
- src: url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff2') format('woff2'),
- url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff') format('woff');
- font-weight: 300;
- font-style: normal;
- font-display: swap;
- }
- @font-face {
- font-family: 'EquityTextB';
- src: url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff2') format('woff2'),
- url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff') format('woff');
- font-weight: 300;
- font-style: italic;
- font-display: swap;
- }
- @font-face {
- font-family: 'EquityTextB';
- src: url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff2') format('woff2'),
- url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff') format('woff');
- font-weight: 700;
- font-style: normal;
- font-display: swap;
- }
-
- @font-face {
- font-family: 'ConcourseT3';
- src: url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff2') format('woff2'),
- url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff') format('woff');
- font-weight: 300;
- font-style: normal;
- font-display: swap;
- }
-
-
- /* http://practice.typekit.com/lesson/caring-about-opentype-features/ */
- body {
- /* http://www.cssfontstack.com/ Palatino 99% Win 86% Mac */
- font-family: "EquityTextB", Palatino, serif;
- background-color: #f0f0ea;
- color: #07486c;
- font-kerning: normal;
- -moz-osx-font-smoothing: grayscale;
- -webkit-font-smoothing: subpixel-antialiased;
- text-rendering: optimizeLegibility;
- font-variant-ligatures: common-ligatures contextual;
- font-feature-settings: "kern", "liga", "clig", "calt";
- }
- pre, code, kbd, samp, var, tt {
- font-family: 'TriplicateT4c', monospace;
- }
- em {
- font-style: italic;
- color: #323a45;
- }
- strong {
- font-weight: bold;
- color: black;
- }
- nav {
- background-color: #323a45;
- color: #f0f0ea;
- display: flex;
- justify-content: space-around;
- padding: 1rem .5rem;
- }
- nav:last-child {
- border-bottom: 1vh solid #2d7474;
- }
- nav a {
- color: #f0f0ea;
- }
- nav abbr {
- border-bottom: 1px dotted white;
- }
-
- h1 {
- border-top: 1vh solid #2d7474;
- border-bottom: .2vh dotted #2d7474;
- background-color: #e3e1e1;
- color: #323a45;
- text-align: center;
- padding: 5rem 0 4rem 0;
- width: 100%;
- font-family: 'ConcourseT3';
- display: flex;
- flex-direction: column;
- }
- h1.single {
- padding-bottom: 10rem;
- }
- h1 span {
- position: absolute;
- top: 1vh;
- left: 20%;
- line-height: 0;
- }
- h1 span a {
- line-height: 1.7;
- padding: 1rem 1.2rem .6rem 1.2rem;
- border-radius: 0 0 6% 6%;
- background: #2d7474;
- font-size: 1.3rem;
- color: white;
- text-decoration: none;
- }
- h2 {
- margin: 4rem 0 1rem;
- border-top: .2vh solid #2d7474;
- padding-top: 1vh;
- }
- h3 {
- text-align: center;
- margin: 3rem 0 .75em;
- }
- hr {
- height: .4rem;
- width: .4rem;
- border-radius: .4rem;
- background: #07486c;
- margin: 2.5rem auto;
- }
- time {
- display: bloc;
- margin-left: 0 !important;
- }
- ul, ol {
- margin: 2rem;
- }
- ul {
- list-style-type: square;
- }
- a {
- text-decoration-skip-ink: auto;
- text-decoration-thickness: 0.05em;
- text-underline-offset: 0.09em;
- }
- article {
- max-width: 50rem;
- display: flex;
- flex-direction: column;
- margin: 2rem auto;
- }
- article.single {
- border-top: .2vh dotted #2d7474;
- margin: -6rem auto 1rem auto;
- background: #f0f0ea;
- padding: 2rem;
- }
- article p:last-child {
- margin-bottom: 1rem;
- }
- p {
- padding: 0 .5rem;
- margin-left: 3rem;
- }
- p + p,
- figure + p {
- margin-top: 2rem;
- }
-
- blockquote {
- background-color: #e3e1e1;
- border-left: .5vw solid #2d7474;
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 1rem;
- margin: 1.5rem;
- }
- blockquote cite {
- font-style: italic;
- }
- blockquote p {
- margin-left: 0;
- }
-
- figure {
- border-top: .2vh solid #2d7474;
- background-color: #e3e1e1;
- text-align: center;
- padding: 1.5rem 0;
- margin: 1rem 0 0;
- font-size: 1.5rem;
- width: 100%;
- }
- figure img {
- max-width: 250px;
- max-height: 250px;
- border: .5vw solid #323a45;
- padding: 1px;
- }
- figcaption {
- padding: 1rem;
- line-height: 1.4;
- }
- aside {
- display: flex;
- flex-direction: column;
- background-color: #e3e1e1;
- padding: 1rem 0;
- border-bottom: .2vh solid #07486c;
- }
- aside p {
- max-width: 50rem;
- margin: 0 auto;
- }
-
- /* https://fvsch.com/code/css-locks/ */
- p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
- font-size: 1rem;
- line-height: calc( 1.5em + 0.2 * 1rem );
- }
- h1 {
- font-size: 1.9rem;
- line-height: calc( 1.2em + 0.2 * 1rem );
- }
- h2 {
- font-size: 1.6rem;
- line-height: calc( 1.3em + 0.2 * 1rem );
- }
- h3 {
- font-size: 1.35rem;
- line-height: calc( 1.4em + 0.2 * 1rem );
- }
- @media (min-width: 20em) {
- /* The (100vw - 20rem) / (50 - 20) part
- resolves to 0-1rem, depending on the
- viewport width (between 20em and 50em). */
- p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
- font-size: calc( 1rem + .6 * (100vw - 20rem) / (50 - 20) );
- line-height: calc( 1.5em + 0.2 * (100vw - 50rem) / (20 - 50) );
- margin-left: 0;
- }
- h1 {
- font-size: calc( 1.9rem + 1.5 * (100vw - 20rem) / (50 - 20) );
- line-height: calc( 1.2em + 0.2 * (100vw - 50rem) / (20 - 50) );
- }
- h2 {
- font-size: calc( 1.5rem + 1.5 * (100vw - 20rem) / (50 - 20) );
- line-height: calc( 1.3em + 0.2 * (100vw - 50rem) / (20 - 50) );
- }
- h3 {
- font-size: calc( 1.35rem + 1.5 * (100vw - 20rem) / (50 - 20) );
- line-height: calc( 1.4em + 0.2 * (100vw - 50rem) / (20 - 50) );
- }
- }
- @media (min-width: 50em) {
- /* The right part of the addition *must* be a
- rem value. In this example we *could* change
- the whole declaration to font-size:2.5rem,
- but if our baseline value was not expressed
- in rem we would have to use calc. */
- p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
- font-size: calc( 1rem + .6 * 1rem );
- line-height: 1.5em;
- }
- p, li, pre, details {
- margin-left: 3rem;
- }
- h1 {
- font-size: calc( 1.9rem + 1.5 * 1rem );
- line-height: 1.2em;
- }
- h2 {
- font-size: calc( 1.5rem + 1.5 * 1rem );
- line-height: 1.3em;
- }
- h3 {
- font-size: calc( 1.35rem + 1.5 * 1rem );
- line-height: 1.4em;
- }
- figure img {
- max-width: 500px;
- max-height: 500px;
- }
- }
-
- figure.unsquared {
- margin-bottom: 1.5rem;
- }
- figure.unsquared img {
- height: inherit;
- }
-
-
-
- @media print {
- body { font-size: 100%; }
- a:after { content: " (" attr(href) ")"; }
- a, a:link, a:visited, a:after {
- text-decoration: underline;
- text-shadow: none !important;
- background-image: none !important;
- background: white;
- color: black;
- }
- abbr[title] { border-bottom: 0; }
- abbr[title]:after { content: " (" attr(title) ")"; }
- img { page-break-inside: avoid; }
- @page { margin: 2cm .5cm; }
- h1, h2, h3 { page-break-after: avoid; }
- p3 { orphans: 3; widows: 3; }
- img {
- max-width: 250px !important;
- max-height: 250px !important;
- }
- nav, aside { display: none; }
- }
-
- ul.with_columns {
- column-count: 1;
- }
- @media (min-width: 20em) {
- ul.with_columns {
- column-count: 2;
- }
- }
- @media (min-width: 50em) {
- ul.with_columns {
- column-count: 3;
- }
- }
- ul.with_two_columns {
- column-count: 1;
- }
- @media (min-width: 20em) {
- ul.with_two_columns {
- column-count: 1;
- }
- }
- @media (min-width: 50em) {
- ul.with_two_columns {
- column-count: 2;
- }
- }
-
- .gallery {
- display: flex;
- flex-wrap: wrap;
- justify-content: space-around;
- }
- .gallery figure img {
- margin-left: 1rem;
- margin-right: 1rem;
- }
- .gallery figure figcaption {
- font-family: 'ConcourseT3'
- }
-
- footer {
- font-family: 'ConcourseT3';
- display: flex;
- flex-direction: column;
- border-top: 3px solid white;
- padding: 4rem 0;
- background-color: #07486c;
- color: white;
- }
- footer > * {
- max-width: 50rem;
- margin: 0 auto;
- }
- footer a {
- color: #f1c40f;
- }
- footer .avatar {
- width: 200px;
- height: 200px;
- border-radius: 50%;
- float: left;
- -webkit-shape-outside: circle();
- shape-outside: circle();
- margin-right: 2rem;
- padding: 2px 5px 5px 2px;
- background: white;
- border-left: 1px solid #f1c40f;
- border-top: 1px solid #f1c40f;
- border-right: 5px solid #f1c40f;
- border-bottom: 5px solid #f1c40f;
- }
- </style>
-
- <h1>
- <span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
- Robust Client-Side JavaScript (archive)
- <time>Pour la pérennité des contenus liés. Non-indexé, retrait sur simple email.</time>
- </h1>
- <section>
- <article>
- <h3><a href="https://molily.de/robust-javascript/">Source originale du contenu</a></h3>
- <p class="separator">❦</p>
-
- <p><h1 id="main-heading"> Robust Client-Side JavaScript <span class="subheading">A Developer’s Guide</span> </h1> <p class="intro"><em>The JavaScript programming language</em> is an essential tool of web developers today. Websites ship more and more JavaScript to the browser to be more interactive. The more complex client-side JavaScript gets, the more error-prone and fragile the user experience might get. Why do we need to talk about robust JavaScript and how do we achieve it?</p> <p class="separator">❡</p> <h2 id="introduction">Introduction</h2> <h3 id="characteristics-of-javascript">Characteristics of JavaScript</h3> <p>In the trinity of front-end web technologies – HTML, CSS and JavaScript –, the latter is different from the others. HTML and CSS are declarative languages for the special purpose of structuring a text document and expressing style rules, respectively. Both HTML and CSS are designed in a way that allows browsers to process the code in a forgiving, fault-tolerant way. These design features are necessary to allow for backward and forward compatibility.</p> <p>JavaScript however is a fully fledged programming language that happens to run in the context of a web page. JavaScript has only a few fail-safe and compatibility mechanisms built in. Whereas JavaScript’s power is unlimited, HTML and CSS have the <a href="https://www.w3.org/2001/tag/doc/leastPower.html">least power</a> that is necessary to serve their special purpose.</p> <p>There are thousands of ways HTML, CSS and JavaScript might fail, and it happens every day, on almost every website. But when HTML or CSS fail, the impact is rather limited. A web page may still be usable in spite of several HTML and CSS errors. In contrast, a single JavaScript error may render the whole website unusable. Sometimes there are ways to recover, but the user might not be aware of them.</p> <p>In this guide, we will investigate why JavaScript might fail and how to prevent or handle these errors in a graceful way that ensures a working website.</p> <h3 id="the-browser-as-a-runtime-environment">The browser as a runtime environment</h3> <p>Writing client-side JavaScript for the web differs from programming for other platforms. There is not one well-defined runtime environment a developer may count on. There is not one hardware architecture or device type. There is not a single vendor that defines and builds the runtime, the compiler and the tools.</p> <p>The web is an open, vendor-independent, heterogenous publishing platform. It is held together by several technical standards of different quality. New standards appear frequently, old standards are amended or deprecated. Different standardization organizations follow different rules of procedure.</p> <p>This has lead to the following situation:</p> <ul> <li>There is technical behavior that is standardized and that major browsers agree on. For example, the basic HTML elements are well-supported.</li> <li>There is technical behavior that is standardized and that major browsers do not agree on. For example, browsers may have bugs in their implementation or simply not support newer standards yet.</li> <li>There is technical behavior that is not standardized and that major browsers agree on. Standards may omit some details, leaving them for implementors to decide. Still, browser vendors copy the detailed behavior from other browsers for consistency.</li> <li>There is technical behavior that is not standardized and that major browsers do not agree on. Typically new web technologies are born as proprietary experiments before entering a standardization process. Some technologies are never widely adopted and fall into oblivion.</li> </ul> <p>There are numerous relevant browsers in numerous versions running on different operating systems on devices with different hardware abilities, internet connectivity, etc. The fact that the web client is not under their control maddens developers from other domains. They see the web as the most hostile software runtime environment. They understand the diversity of web clients as a weakness.</p> <p>Proponents of the web counter that this heterogeneity and inconsistency is in fact a strength of the web. The web is open, it is everywhere, it has a low access threshold. The web is adaptive and keeps on absorbing new technologies and fields of applications. No other software environment so far has demonstrated this degree of flexibility.</p> <p>Front-end developers benefit from a web that keeps on evolving and innovating. Especially JavaScript developers may quickly adopt new language features as soon as they are specified and implemented by some browsers. In this guide, we will explore how to use new features in a backwards-compatible way.</p> <p>It is true that client-side JavaScript programming is a minefield. But there is a simple, Socratic principle that will light our way: <em>Do not take anything for granted. Do not count on anything. Question your beliefs.</em> If you know that you know nothing about the client that runs your JavaScript code, you can turn unfounded assumptions into justified knowledge.</p> <p>Assumptions are necessary and inevitable in JavaScript, but we need to <a href="https://remysharp.com/2015/07/02/assumptions">own these assumptions</a>. Every JavaScript program makes a lot of assumptions about its runtime environment. While having a low entry barrier is certainly desirable, the program needs to fulfill a task. The requirements should be in a well-balanced relation to the provided features.</p> <h3 id="javascript-standards">JavaScript standards</h3> <p>There is no single technical specification that defines JavaScript, but a whole bunch of specifications.</p> <p>The <a href="https://www.ecma-international.org/publications/standards/Ecma-262.htm">ECMAScript specification</a> defines the core of the language: the basic language features, the syntax, the execution and the standard library. A new version of ECMAScript is published every year, and <a href="https://www.ecma-international.org/ecma-262/8.0/">ECMAScript 2017, Edition 8</a>, also called ECMAScript 8, is the latest at the time of writing.</p> <p>With ECMAScript alone, you cannot do anything useful. For example, there is no way to read or output any data. ECMAScript does not define the so-called <dfn>host environment</dfn> in which a program is executed. It allows several possible host environments. An HTML document in the browser is one possible host environment. Node.js is another popular one.</p> <p>The host environment we are interested in here is primarily defined in the <a href="https://www.w3.org/TR/html5/">HTML specification</a>. It not only defines HTML as a markup language, it also defines how JavaScript is executed in the context of an HTML document. It defines how JavaScript can access and alter the document. For this purpose, it relies on yet another specification: the <a href="https://dom.spec.whatwg.org/">Document Object Model (DOM)</a>.</p> <p>The HTML and DOM specifications define the main objects that client-side JavaScript is dealing with: nodes, elements and events. Fundamental objects include <code class="highlighter-rouge">window</code>, <code class="highlighter-rouge">window.alert()</code>, <code class="highlighter-rouge">document</code>, <code class="highlighter-rouge">document.body</code>, <code class="highlighter-rouge">document.getElementById()</code> and <code class="highlighter-rouge">document.createElement()</code>.</p> <p>There are a lot of other specifications that add more APIs to the browser’s JavaScript environment. <a href="https://platform.html5.org/">The web platform: Browser technologies</a> gives an overview.</p> <h3 id="the-global-object-window">The global object <code class="highlighter-rouge">window</code></h3> <p>The most important ECMAScript object is the <dfn>global object</dfn>. In the browser, the global object is <code class="highlighter-rouge">window</code>. It is not only the top-most object representing the current browsing instance, it also forms the top-most scope for names defined by the developer.</p> <p>These names are called “bindings” in ECMAScript terminology. They include, among others, global variables like <code class="highlighter-rouge">var fooVariable = 1;</code>, functions declarations like <code class="highlighter-rouge">function fooFunction() {}</code> and class declarations like <code class="highlighter-rouge">class FooClass {}</code>. When this code is executed in the global scope, properties on the global object <code class="highlighter-rouge">window</code> are created: <code class="highlighter-rouge">window.fooVariable</code>, <code class="highlighter-rouge">window.fooFunction</code> and <code class="highlighter-rouge">window.FooClass</code>.</p> <p>Understanding scope is crucial since all scripts running on a web page share the same global scope. A script needs to be careful to not conflict with built-in <code class="highlighter-rouge">window</code> properties – there are hundreds – and properties created by other scripts.</p> <h3 id="how-javascript-is-executed">How JavaScript is executed</h3> <p>JavaScript is typically embedded into an HTML document either directly with a <code class="highlighter-rouge"><script> … </script></code> element, or it is referenced externally with <code class="highlighter-rouge"><script src="…"></script></code>. Scripts may load other scripts dynamically.</p> <p>The HTML specification has a lengthy definition on how scripts are loaded and executed. The gist is that normal scripts are downloaded in parallel but are executed one after another in the order they are referenced in the HTML. Such synchronous scripts block the parsing of the HTML code since they may insert new code into the HTML stream using <code class="highlighter-rouge">document.write()</code>.</p> <p>Nowadays this is a performance anti-pattern. Scripts should be loaded asynchronously using <code class="highlighter-rouge"><script src="…" defer></script></code> or <code class="highlighter-rouge"><script src="…" async></script></code>. And <code class="highlighter-rouge">document.write()</code> should be avoided altogether. This allows the HTML parser to do its job without being interrupted by JavaScript.</p> <p>Mind that the JavaScript engine is still single-threaded, so only one script or function is executed at a given time. (<a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers">Web workers</a> are an exception to this rule.) Also the JavaScript execution happens in the browser tab’s main thread, which means in the worst case it freezes the whole page.</p> <p class="separator">❡</p> <h2 id="achieving-robustness">Achieving Robustness</h2> <p>What does robustness mean? In everyday language, a thing is considered robust when it is made of solid, strong material that somehow resists to applied force. You can use it for a long time, you can drop it by accident, you can even hit on it with a hammer or throw it around, but it does not break.</p> <p>According to this definition, a piece of hard metal may be robust, but an elastic bouncy ball as well. Science looks for materials that combine strength with a certain resistance to force, like <a href="https://simple.wikipedia.org/wiki/Ductility">ductility</a> and elasticity.</p> <p>A structure can also be robust. Think of a <a href="https://en.wikipedia.org/wiki/Lattice_tower">lattice tower</a> that is using a certain type of <a href="https://en.wikipedia.org/wiki/Truss">truss</a>. It is huge and strong, yet light and modular.</p> <p>Similarly, in computer science, a robust program <q cite="http://www.linfo.org/robust.html">performs well not only under ordinary conditions but also under unusual conditions that stress its designers’ assumptions.</q> (<a href="http://www.linfo.org/robust.html">The Linux Information Project</a>). The program does not stop execution when errors occur. It does not fail when the input data or user input is invalid or bogus.</p> <p>So robustness is all about <em>making informed assumptions</em>. What happens when the developer’s assumptions are not met? Let us have a look at several concepts of robustness.</p> <h3 id="graceful-degradation">Graceful Degradation</h3> <p>In the context of web development, Graceful Degradation means building a full-featured website, than adding fallbacks for clients that lack certain capabilities.</p> <p>A website starts with a large, fixed set of features and, consequently, a large set of requirements. The client may not meet a requirement, so a feature depending on it may not be available. If a requirement is not met, the site does not break, but handles the situation gracefully. For example, it falls back to a simpler version.</p> <p>For JavaScript, Graceful Degradation could mean to implement the site using the latest JavaScript language features and APIs. But every usage must be guarded by a capability check. In case the browser does not have the required capability, a simpler implementation is activated.</p>
- <h3 id="progressive-enhancement">Progressive Enhancement</h3> <p>Progressive Enhancement is similar to Graceful Degradation, but turns the process around.</p> <p>A website still has a set of desired features and a set of client requirements. But the implementation starts with a minimal set of features. A first version of the site has a low entry barrier because it only uses well-established technologies.</p> <p>Then, a second version is built that <em>enhances</em> the first version by adding new features. The enhanced version checks whether the client supports certain web technologies, then uses them safely. If the client meets the requirements for a feature, that feature is activated.</p> <p>This process is repeated, creating a third, fourth, fifth version and so on. That is why it is called <em>Progressive</em> Enhancement. In theory, the website can be enhanced endlessly while staying robust and accessible to devices and browsers with restricted capabilities.</p> <h3 id="graceful-degradation-vs-progressive-enhancement">Graceful Degradation vs. Progressive Enhancement</h3> <p>Graceful Degradation and Progressive Enhancement are two implementations of the same idea, but with a different twist.</p> <p>Graceful Degradation aims for the full experience using bleeding-edge technologies – the <a href="http://whatis.techtarget.com/definition/moonshot">moonshot</a>. Building a perfect site takes a lot of time and resources. When such a page is built, it typically works only in one browser on the newest devices.</p> <p>Then sophisticated fallbacks need to be added after the fact. This turns out to be difficult and tedious. For certain new browser features, developing equivalent fallbacks is virtually impossible. But more importantly, adding fallbacks is often neglected. When the budget is almost exhausted, web developers tend to add “this site requires browser X” signs, excluding many users, instead of writing proper fallbacks.</p> <p>Progressive Enhancement in contrast follows the “<a href="https://en.wikipedia.org/wiki/Minimum_viable_product">minimal viable product</a>” school of product development. The goal is to publish a working website quickly. This first version is not the most user-friendly, and certainly not the shiniest among its competitors. But the site works smoothly on every device. What it does, it does reliably.</p> <p>Enhancements that make use of the latest browser features can now be added safely and deployed quickly. There is no stage during development in which the site only works for a small fraction of users.</p> <p>It is widely agreed that Progressive Enhancement offers more benefits, but when applied to an individual website both methods should be considered and even mixed.</p> <p>If you are planning a “moonshot” that relies on bleeding-edge technology in its core experience, like video conferencing or augmented reality, Graceful Degradation may help you to build a more inclusive site.</p> <p>If you are planning a service with a rock-solid base and demanding extras, like realtime data analysis and visualization, Progressive Enhancement may help you to build high without losing accessibility.</p> <p>When applied to JavaScript programming, both Graceful Degradation and Progressive Enhancement raise a lot of practical questions. How is a fallback applied? How well does it integrate with the rest of the code? To which extent is it possible to built on an existing version and enhance it? Is it not sometimes necessary to make a clear cut? You need to find answers that are specific for your project.</p> <p>Both Graceful Degradation and Progressive Enhancement rely on checking the client’s capabilities. The crucial technique we are going to discuss later is called <em><a href="#feature-detection">feature detection</a></em>.</p> <h3 id="fault-tolerance">Fault tolerance</h3> <p>Another concept of robustness is <dfn>fault tolerance</dfn>. A technical system is considered fault-tolerant when the whole system continues to operate even if sub-systems fail.</p> <p>A system consists of critical sub-systems and non-critical sub-systems. Critical sub-systems provide infrastructure and orchestrate the other parts. If they fail, typically the whole system fails. In contrast, non-critical sub-systems may recover from an error. Or they shut down in a controlled way and report the shutdown to allow for backup systems to take over.</p> <p>While Graceful Degradation and Progressive Enhancement are native principles of web technologies, fault tolerance is not. It is probably the hardest yet most beneficial technique for achieving robustness.</p> <p>In particular, fault tolerance is hard to implement in JavaScript. Used without caution, JavaScript is <em>the opposite of fault-tolerant</em>. Usually, if one operation fails, if one exception occurs, the whole call stack or the whole program blows up.</p> <p>Implementing fault tolerance in JavaScript means dividing the code into independent, sandboxed sub-systems. Only few of them are critical. Most of them should be non-critical. If the latter fail with an error, the error needs to be caught and handled. Other sub-systems and the system as a whole should not be affected.</p> <p>JavaScript does not support the definition of native sandboxes yet, but we can employ existing techniques like <a href="#handling-exceptions-with-trycatch">try…catch</a> to achieve the desired effect.</p>
- <h3 id="postels-law">Postel’s Law</h3> <p>John Postel was a computer scientist that helped designing the core technologies of the internet. He edited the technical specifications of fundamental internet protocols, called Request for Comments (RFC).</p> <p>In <a href="https://tools.ietf.org/html/rfc760">RFC 790</a>, published in January 1980, Postel first described the Internet Protocol (IPv4). There is a precise description of how implementations should behave:</p> <blockquote cite="https://tools.ietf.org/html/rfc760"> <p>The implementation of a protocol must be robust. Each implementation must expect to interoperate with others created by different individuals. While the goal of this specification is to be explicit about the protocol there is the possibility of differing interpretations. In general, an implementation should be conservative in its sending behavior, and liberal in its receiving behavior. That is, it should be careful to send well-formed datagrams, but should accept any datagram that it can interpret (e.g., not object to technical errors where the meaning is still clear).</p> </blockquote> <p>In <a href="https://tools.ietf.org/html/rfc761">RFC 761</a>, also published in January 1980, Postel described the Transmission Control Protocol (TCP) as used by the United States Department of Defense:</p> <blockquote cite="https://tools.ietf.org/html/rfc761"> <p>TCP implementations should follow a general principle of robustness: be conservative in what you do, be liberal in what you accept from others.</p> </blockquote> <p>Today this principle is often called <dfn>Postel’s Law</dfn>. While the original context was very specific – processing packets on a wide-area computer network –, today it is applied to all programs that read, parse and process user input, file formats or other structured data.</p> <p>For example, the liberal, fault-tolerant <a href="https://www.w3.org/TR/html5/syntax.html#parsing-html-documents">HTML 5 parser definition</a> along with the conservative <a href="https://www.w3.org/TR/html5/syntax.html#writing-html-documents">HTML 5 syntax definition</a> is an application of Postel’s Law.</p> <p>Personally, I do not think Postel’s Law should be seen as a <q>general principle of robustness</q>. I agree to some point that a program should accept data <q>that it can interpret (e.g. not object to technical errors where the meaning is still clear)</q>. This rule requires careful interpretation.</p> <p>In this guide, I do not argue that every program should be liberal in what it accepts. I find it more important that every program is explicit about what it accepts, is outspoken about technical errors and has a well-defined error handling.</p> <p class="separator">❡</p>
- <h2 id="how-javascript-might-fail">How JavaScript might fail</h2> <h3 id="web-crawlers-without-javascript-support">Web crawlers without JavaScript support</h3> <p>Adding JavaScript to a website assumes that the client downloads and executes the code. This is not the case for a lot of automated web clients. Most robots and web crawlers speak HTTP, HTML and probably some CSS, but usually not JavaScript.</p> <p>Some try to forage the JavaScript code for URLs or other valuable information. Some try to analyze the JavaScript to find malware or security vulnerabilities. Some even try to execute JavaScript in a fake browser environment.</p> <p>What these robots have in common is that they are not interested in JavaScript per se. JavaScript typically makes a web page interactive, but a robot aims to analyze the page without simulating user interaction.</p> <p>A search engine for example needs to evaluate if a page is valuable with regard to a query. So a search engine crawler is interested in text content, semantic markup, hyperlinks and probably media files.</p> <p>Such a crawler wants simple code that it can parse quickly to find valuable data. Like HTML code. Executing arbitrary JavaScript is complex, slow and a potential a security risk. Some crawlers might do it anyhow, but just as a way to find text content, semantic markup, hyperlinks, etc.</p> <p>If a site cares for a decent search engine ranking, it should make it easy for crawlers to find meaningful, unique, structured text content. HTML is the best technology to present such content. This means the relevant content should be accessible without JavaScript, just by looking at the HTML returned by the server. All content should be reachable by plain hyperlinks, like <code class="highlighter-rouge"><a href="…">…</a></code>.</p> <p>For complex interactivity and content that cannot or should not be read by robots, it is fine to require JavaScript.</p>
- <h3 id="disabled-javascript-execution">Disabled JavaScript execution</h3> <p>While robots avoid running JavaScript, humans typically use a browser that runs JavaScript. Almost all browsers today have the capability to run JavaScript. But the user or their administrator may allow only JavaScript of certain origins or may have disabled JavaScript execution completely.</p> <p>There are good security reasons for disabling the execution of arbitrary JavaScript. Since JavaScript is a fully fledged programming language, processing it is more complex and error-prone than any other format on the web. The browser exposes several critical APIs to JavaScript code. In consequence, JavaScript is the most frequent attack vector for browser exploits.</p> <p>JavaScript is also used to invade the user’s privacy. Especially the advertisement industry gathers and combines information that is obtained using JavaScript across different sites. JavaScript APIs allow reading details about the machine’s hardware and software as well as saving data on the machine. These features are abused to create a unique “fingerprint” and an extensive profile of the user: visited sites, search terms, purchase history, interests; also age, gender, location, marital status, profession, income, ethnicity, political views, etc.</p> <p>To protect the users, ad and privacy blockers as well as corporate web proxies may ignore the JavaScript from certain hosts or limit the access to certain JavaScript APIs. Some security proxies even change the author’s JavaScript code.</p> <p>JavaScript authors need to learn how blockers and web proxies work. They typically match the URL with a whitelist or blacklist. Make sure the host (<code class="highlighter-rouge">example.org</code>) serving the JavaScript is not on a blacklist. In a corporate intranet with a whitelist, make sure the host is on the whitelist. Also avoid suspicious patterns in the URL path that could trigger the blocking, like <code class="highlighter-rouge">ad.js</code>.</p> <p>Since ads and privacy-invading scripts are typically loaded from third-party servers, blockers tend to allow JavaScript from the same domain and likely block JavaScript from a different domain. Make sure your scripts are placed on the same domain, a custom domain for assets, or a well-known, trusted content delivery network (see next chapter).</p>
- <h3 id="network-and-loading-errors">Network and loading errors</h3> <p>In the age of mobile web access, a flaky internet connection is the norm. The connections from the client to a server are interrupted frequently. Sometimes the browser re-establishes the connections to send requests again. Sometimes the user needs to reload the page manually so all parts are fully loaded.</p> <p>Network interruptions affect JavaScript more negatively than other formats on the web. HTML, CSS, images and videos can be loaded and processed incrementally. If half of the HTML code has been transmitted and the connection drops, the browser can still render half of the page. Image formats like JPEG and PNG have progressive modes so the user gets to see a low-resolution preview after 10-20% of the file have been transmitted.</p> <p>For JavaScript, it is all or nothing. To execute the JavaScript, the full script needs to be transmitted.</p> <p>JavaScript authors can do little against connectivity loss. But they can prepare for the case by shipping fewer and smaller scripts, and by making JavaScript optional for key content.</p> <p>One way to improve the loading performance of scripts is to host JavaScript on <dfn>content delivery networks</dfn> (CDN). These are arrays of well-connected servers distributed around the globe optimized for caching and serving static assets like CSS, JavaScript and media files. When the browser requests an asset, the request is automatically routed to the nearest <abbr title="content delivery networks">CDN</abbr> server.</p> <p>For example, if a user in Indonesia visits a site hosted in Europe, the network latency slows down the transfer. With a <abbr title="content delivery networks">CDN</abbr> server in Indonesia, the assets can be served more quickly, lowering the risk of connection interruption.</p> <p>Apart from network connectivity problems, an HTTP request for a script can fail for other obvious reasons: 404 Not found, 500 Server error, etc. This seems trivial but these types of errors are probably the most common. Monitor the server log to catch these errors. Use tools to find broken links and check the output of web crawlers like the <a href="https://www.google.com/webmasters/tools/home?hl=en">Google search robot</a>.</p> <h3 id="parsing-errors">Parsing errors</h3> <p>The <dfn>parser</dfn> is the part of the browser’s JavaScript engine that reads the JavaScript source code sequentially to build an in-memory representation of the syntax. While the JavaScript code is just a gibberish stream of characters, the engine needs to transform it into a usable data structure in order to execute it later.</p> <p>What is syntax again? It is the set of rules in a language that allow us to form a meaningful and correct sentence.</p> <p>For example, if you read the sentence “The dog wags its tail”, you may think of a friendly Golden Retriever. A linguist does the same, but involuntarily starts to dissect the sentence, breaking it up into pieces and their relation.</p> <p>The sentence is made of a noun phrase and a verb phrase. The noun phrase, “the dog”, consists of a determiner and a noun. The verb phrase, “wags its tail”, consists of verb and a noun phrase again. The verb, “wags”, has the third person singular present form. And so on.</p> <p>For JavaScript, it is quite similar, yet less familiar since JavaScript is a not a natural language, but an artificial computer language. If you write <code class="highlighter-rouge">window.alert('Hello World!');</code>, the parser generates an <dfn>Abstract Syntax Tree</dfn> (AST) that may look like this:</p><pre>
- <strong>Program</strong>
- <strong>ExpressionStatement</strong>
- <strong>CallExpression</strong>
- callee:
- <strong>MemberExpression</strong>
- object:
- <strong>Identifier</strong>
- <em>window</em>
- property:
- <strong>Identifier</strong>
- <em>alert</em>
- arguments:
- <strong>Literal</strong>
- value: <em>"Hello World!"</em>
- </pre><p>We will not go into detail here, but let us describe the structure of the program <code class="highlighter-rouge">window.alert('Hello World!');</code> in our own words:</p> <p>There is an expression (think of a mathematical term) with a call of a function. To obtain this function, we need to look up the name <code class="highlighter-rouge">window</code>. We assume the value is an object and get its property named <code class="highlighter-rouge">alert</code>. We treat this value as the function being called. There is one function argument, a string literal containing <code class="highlighter-rouge">Hello World!</code>.</p> <p>To execute a script, the JavaScript engine needs such a high-level format, not the low-level code consisting of letters, dots, braces, brackets, semicolons, etc.</p> <p>If you make a slip of the tongue, a gentle listener will probably ask: “Pardon me, what did you mean by ‘alert Hello World’?” The JavaScript parser is not that polite. It has a draconian, unforgiving error handling. If it encounters a character that is not expected in a certain place, it immediately aborts parsing the current script and throws a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError">SyntaxError</a>. So one misplaced character, one slip of the pen can ruin your script.</p> <p>The most frequent syntax error is probably due to typos in hand-written code. Fortunately, these errors are easy to prevent by using an editor with syntax checking or a <a href="#linters">linter</a>.</p> <p>Even with these safeguards in place, syntax errors occur. There are several ECMAScript versions with different syntaxes. For example, if you use class declarations from ECMAScript 6 (2015) or <code class="highlighter-rouge">async</code>/<code class="highlighter-rouge">await</code> from ECMAScript 8 (2017), older browsers are not able to parse your script.</p> <p>The standard solution is to <a href="#the-babel-compiler">compile</a> newer ECMAScript syntax into an older equivalent syntax that is widely supported, usually ECMAScript 3 or 5.</p> <h3 id="exceptions">Exceptions</h3> <p>You may have heard of exceptions in the context of JavaScript, but what are they?</p> <p>“Exception” does not mean exception to any rule here. An exception is an exceptional error, a fatal error. A program error that the JavaScript engine cannot handle on its own. If such an error occurs, the program is aborted. More specifically, the current function <a href="https://en.wikipedia.org/wiki/Call_stack">call stack</a> is aborted. It is still possible to call the same function or other functions later.</p> <p>There are several causes for exceptions, and we are already encountered one: The SyntaxError occurs during parsing, before your code is even executed. Let us look at two common exceptions that may happen when the code is run: The <code class="highlighter-rouge">ReferenceError</code> and the <code class="highlighter-rouge">TypeError</code>.</p> <h3 id="reference-errors">Reference errors</h3> <p>A <code class="highlighter-rouge">ReferenceError</code> is thrown when the program references a name – an identifier in ECMAScript terminology – that cannot be resolved to a value.</p> <p>First, let us look at successful references:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">name</span> <span class="o">=</span> <span class="s1">'Kitty'</span><span class="p">;</span>
- <span class="nb">window</span><span class="p">.</span><span class="nx">alert</span><span class="p">(</span><span class="s1">'Hello '</span> <span class="o">+</span> <span class="nx">name</span><span class="p">);</span>
- </code></pre></div> </div> <p>We have two references here, <code class="highlighter-rouge">window.alert</code> and <code class="highlighter-rouge">name</code>. To resolve them to values, the JavaScript engine first looks for the identifiers <code class="highlighter-rouge">window</code> and <code class="highlighter-rouge">name</code> in the <a href="http://ryanmorr.com/understanding-scope-and-context-in-javascript/">scope chain</a>.</p> <p><code class="highlighter-rouge">window</code> is a global identifier, a property of the global object, as we have learned. After having resolved <code class="highlighter-rouge">window</code> to an object, the JavaScript engine looks for a property <code class="highlighter-rouge">alert</code> on this object.</p> <p><code class="highlighter-rouge">name</code> is a local or global variable, depending on the context.</p> <p>Now, let us look at erroneous references:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">window</span><span class="p">.</span><span class="nx">alert</span><span class="p">(</span><span class="nx">frobnicateFoo</span><span class="p">);</span>
- </code></pre></div> </div> <p>The identifier <code class="highlighter-rouge">frobnicateFoo</code> cannot be found in the scope chain. So the JavaScript engine throws a ReferenceError: “frobnicateFoo is not defined”.</p> <p>So ReferenceErrors happen when the code uses an identifier that cannot be found in the current scope and all parent scopes. This is may be due to a typo. <a href="#linters">Linters</a> can catch these bugs easily.</p> <p>Another possible cause is the developer assuming that the browser supports a certain API. The developer assumes a global identifier is provided and uses it without caution. These are several examples that assume the availability of certain browser APIs:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">object</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">string</span><span class="p">);</span>
- </code></pre></div> </div> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="s1">'name'</span><span class="p">,</span> <span class="s1">'Kitty'</span><span class="p">);</span>
- </code></pre></div> </div> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">promise</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="p">{</span>
- <span class="cm">/<em> … </em>/</span>
- <span class="p">});</span>
- </code></pre></div> </div> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">fetch</span><span class="p">(</span><span class="s1">'/something'</span><span class="p">)</span>
- <span class="p">.</span><span class="nx">then</span><span class="p">(</span>
- <span class="kd">function</span><span class="p">(</span><span class="nx">response</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/<em> … </em>/</span> <span class="p">},</span>
- <span class="kd">function</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/<em> … </em>/</span> <span class="p">}</span>
- <span class="p">);</span>
- </code></pre></div> </div> <p><a href="https://caniuse.com/#feat=json">JSON</a> is available in 98.14% of the browsers, <a href="https://caniuse.com/#feat=namevalue-storage">localStorage</a> in 95.31%, <a href="https://caniuse.com/#feat=promises">Promise</a> in 89.04%, <a href="https://caniuse.com/#feat=fetch">fetch</a> in 77.81%.</p> <p>We can avoid such careless use of APIs by using <em>feature detection</em>. In particular, we need to check for the names we intent to use.</p> <p>Writing good feature checks requires thorough knowledge of the API being used. We will go into details later <a href="#feature-detection">in its own chapter</a>. This is how we can guard the API uses above:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">JSON</span> <span class="o">===</span> <span class="s1">'object'</span> <span class="o">&&</span>
- <span class="k">typeof</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span> <span class="o">===</span> <span class="s1">'function'</span><span class="p">)</span> <span class="p">{</span>
- <span class="cm">/<em> Call JSON.parse() </em>/</span>
- <span class="p">}</span>
- </code></pre></div> </div> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span>
- <span class="k">typeof</span> <span class="nx">localStorage</span> <span class="o">===</span> <span class="s1">'object'</span> <span class="o">&&</span>
- <span class="k">typeof</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span> <span class="o">===</span> <span class="s1">'function'</span>
- <span class="p">)</span> <span class="p">{</span>
- <span class="cm">/<em> Call localStorage.setItem() </em>/</span>
- <span class="p">}</span>
- </code></pre></div> </div> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nb">Promise</span> <span class="o">===</span> <span class="s1">'function'</span><span class="p">)</span> <span class="p">{</span>
- <span class="cm">/<em> Call new Promise() </em>/</span>
- <span class="p">}</span>
- </code></pre></div> </div> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">fetch</span> <span class="o">===</span> <span class="s1">'function'</span><span class="p">)</span> <span class="p">{</span>
- <span class="cm">/<em> Call fetch() </em>/</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>These guards are only the first step. They check whether the API objects exist and have a certain type, like function. They do not check whether the browser has full and correct support of the API. They do not check whether the APIs can be used in the current context.</p> <p>For example, security and privacy preferences may limit the usage of APIs like <code class="highlighter-rouge">localStorage</code> or <code class="highlighter-rouge">fetch</code>. Each API defines its own way how to deal with failure, like <a href="#handling-exceptions-with-trycatch">throwing an exception</a> or returning a value denoting an error.</p> <h3 id="type-errors">Type errors</h3> <p>A <code class="highlighter-rouge">TypeError</code> is thrown when a program tries to perform an operation with a value whose type is not suitable for this operation. In other words, when you try to do something with a value that you cannot do with the value.</p> <p>For example, functions can be called with the call operator <code class="highlighter-rouge">(…)</code>. All other values, like strings, numbers or plain objects cannot be called. All these examples fail with a TypeError because the value on the left side of the braces is not a function:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s2">"a string"</span><span class="p">();</span>
- <span class="mi">5</span><span class="p">();</span>
- <span class="p">({})();</span>
- <span class="kc">undefined</span><span class="p">();</span>
- <span class="kc">null</span><span class="p">();</span>
- </code></pre></div> </div> <p>This seems obvious. Why would you try to call a number as if it was a function? You would not do that on purpose when writing the code, but it happens in production. Let us look at this example:</p> <p>Here, we have a reference to a property <code class="highlighter-rouge">frobnicateFoo</code> on the object <code class="highlighter-rouge">window</code>. Resolving <code class="highlighter-rouge">window</code> yields the global object. But there is no property <code class="highlighter-rouge">frobnicateFoo</code> on this very object. If you get the value of a non-existing property, JavaScript does not throw an exception, it simply returns <code class="highlighter-rouge">undefined</code>. So after resolving <code class="highlighter-rouge">window.frobnicateFoo</code>, the code is equivalent to <code class="highlighter-rouge">undefined();</code>.</p> <p>Such TypeErrors are both common and hard to debug since they may have highly different causes.</p> <p>In the example above, the cause is the use of a function without checking its existence beforehand. <code class="highlighter-rouge">frobnicateFoo</code> might be a user-defined function or a part of a browser API. If the function call fails because the function does not exist, the script defining the function was not loaded correctly or the browser does not support the API.</p> <p>Here is another example of a similar “undefined is not a function” TypeError.</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">myLibrary</span> <span class="o">=</span> <span class="p">{</span>
- <span class="nx">start</span><span class="p">()</span> <span class="p">{</span> <span class="cm">/<em> … </em>/</span> <span class="p">}</span>
- <span class="p">};</span>
- <span class="nx">myLibrary</span><span class="p">.</span><span class="nx">statr</span><span class="p">();</span>
- </code></pre></div> </div> <p>The problem here is a simple typo. <code class="highlighter-rouge">myLibrary.start</code> is a function, but <code class="highlighter-rouge">myLibrary.statr</code> returns <code class="highlighter-rouge">undefined</code>.</p> <p>These errors can be avoided by <a href="#manual-testing">manual</a> and <a href="#automated-testing">automated testing</a> as well static code analysis. An <abbr title="Integrated development environments">IDEs</abbr> for example understands that the code defines an object <code class="highlighter-rouge">myLibrary</code> with the single property <code class="highlighter-rouge">start</code>. When it encounters <code class="highlighter-rouge">myLibrary.statr</code>, it shows a warning because it does not recognize the property <code class="highlighter-rouge">statr</code>.</p> <p>There are several other cases where TypeErrors are thrown. For example when you try to redefine the value of a constant:</p> <p>As William Shakespeare famously wrote in his sonnets about JavaScript, an immutable binding is not an immutable binding …</p> <blockquote> <p>Which alters when it alteration finds,<br/> Or bends with the remover to remove:<br/> O, no! it is an ever-fixed mark,<br/> That looks on tempests and is never shaken</p> </blockquote> <p>The nature of a constant is that its value cannot be changed later, so using the assignment operator <code class="highlighter-rouge">=</code> with the constant <code class="highlighter-rouge">a</code> on the left side throws a TypeError: “invalid assignment to const "a"”.</p> <p>Similarly, a TypeError is thrown when you try to add a property to an object that does not allow the addition of properties:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">MyLibrary</span> <span class="o">=</span> <span class="p">{</span>
- <span class="nx">start</span><span class="p">()</span> <span class="p">{}</span>
- <span class="p">};</span>
- <span class="nb">Object</span><span class="p">.</span><span class="nx">seal</span><span class="p">(</span><span class="nx">MyLibrary</span><span class="p">);</span>
- <span class="nx">MyLibrary</span><span class="p">.</span><span class="nx">newProperty</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
- </code></pre></div> </div> <p>In <a href="#the-strict-mode">Strict Mode</a>, this code throws a TypeError “can’t define property "newProperty": Object is not extensible”. Without the strict mode, the new property is silently ignored.</p> <p>The same goes for overwriting properties which are read-only:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">MyLibrary</span> <span class="o">=</span> <span class="p">{</span>
- <span class="nx">start</span><span class="p">()</span> <span class="p">{}</span>
- <span class="p">};</span>
- <span class="nb">Object</span><span class="p">.</span><span class="nx">freeze</span><span class="p">(</span><span class="nx">MyLibrary</span><span class="p">);</span>
- <span class="nx">MyLibrary</span><span class="p">.</span><span class="nx">start</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{};</span>
- </code></pre></div> </div> <p>In strict mode, this code throws a TypeError “"start" is read-only”. Without the strict mode, the assignment is silently ignored.</p> <p>Again, these errors can only be avoided by manual and automated testing.</p> <h3 id="security-errors">Security errors</h3> <p>There is no common error type for security errors in ECMAScript. Browser APIs throw several types of errors when API access is disallowed. Some APIs wrap the error in a Promise that is accessible in the rejection handler. Here are some examples:</p> <ul> <li> <p>You try to use <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage">localStorage</a> to save data persistently, but the user has disabled data saving for the site. Merely accessing the property <code class="highlighter-rouge">window.localStorage</code> throws a SecurityError.</p> </li> <li> <p>You try to read the current location using <a href="https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition">navigator.geolocation.getLocation()</a>, but the user declines. The error callback is called with a PositionError with the code 1 (PERMISSION_DENIED).</p> </li> <li> <p>You try to fetch a URL from a different domain using <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">fetch()</a>, but the remote server does not allow it via <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">CORS</a>. The returned promise is rejected with a TypeError.</p> </li> <li> <p>You ask for the permission to show notifications using <a href="https://developer.mozilla.org/en-US/docs/Web/API/Notification/requestPermission">Notification.requestPermission()</a>, but the user declines. The returned promise is resolved with the string “denied” (yes, you read correctly).</p> </li> <li> <p>You try to access the device’s camera using <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia">navigator.mediaDevices.getUserMedia()</a>, but the user declines. The returned promise is rejected with a NotAllowedError.</p> </li> </ul> <p>As you can see, handling security errors requires a careful study of a particular API documentation.</p> <p class="separator">❡</p> <h2 id="how-to-prevent-failure">How to prevent failure</h2> <p>After this short glance at the different types of JavaScript errors, we got an idea of the problem and mentioned some possible solutions. Now let us go into detail about the techniques that prevent JavaScript from failing and handle errors gracefully.</p> <h3 id="failing-fast">Failing fast</h3> <p>Every computer program may have logic bugs: A case is not considered, the state is changed incorrectly, data is transformed wrongly, input is not handled. These bugs can have several consequences in JavaScript:</p> <p>In the <em>best case</em> the script fails with an exception. You may wonder, why is that the best case? Because an exception is visible and easy to report. The line of code that threw an exception is likely not the root cause, but the cause is somewhere in the call stack. An exception is a good starting point for debugging.</p> <p>In the <em>worst case</em> the application continues to run despite the error, but some parts of the interface are broken. Sometimes the user gets stuck. Sometimes data gets lost or corrupted permanently.</p> <p>JavaScript code should <strong><a href="https://martinfowler.com/ieeeSoftware/failFast.pdf">fail fast</a></strong> (PDF) to make errors visible. Failing early with an exception, even with a user-facing error, is better than failing silently with undefined, puzzling behavior.</p> <p>Unfortunately, JavaScript does not follow the principle of failing fast. JavaScript is a <a href="https://en.wikipedia.org/wiki/Strong_and_weak_typing">weakly typed language</a> that goes great lengths to not fail with an error. Most importantly, JavaScript performs <dfn>implicit type conversion</dfn>.</p> <p>Let us look at a simple, contrived example:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">sum</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="p">{</span>
- <span class="k">return</span> <span class="nx">a</span> <span class="o">+</span> <span class="nx">b</span><span class="p">;</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>This function expects two numbers and returns their sum. The implicit assumption is that both arguments, <code class="highlighter-rouge">a</code> and <code class="highlighter-rouge">b</code>, are numbers. If one of them is not, the result <em>probably</em> will not be a number either. Whether the function works correctly depends on correct input types.</p> <p>The problem is, the <code class="highlighter-rouge">+</code> operator <em>is a dangerous beast</em>. Its purpose is to add two numbers, but also to concatenate two strings. If the operands are not two numbers or two strings, implicit type conversion is performed.</p> <p>These rules are specified in ECMAScript, but you should try to avoid ambiguous implicit type conversion. Here is an improved version of the function:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">sum</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="p">{</span>
- <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="k">typeof</span> <span class="nx">a</span> <span class="o">===</span> <span class="s1">'number'</span> <span class="o">&&</span> <span class="o">!</span><span class="nb">isNaN</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="o">&&</span>
- <span class="k">typeof</span> <span class="nx">b</span> <span class="o">===</span> <span class="s1">'number'</span> <span class="o">&&</span> <span class="o">!</span><span class="nb">isNaN</span><span class="p">(</span><span class="nx">b</span><span class="p">)))</span> <span class="p">{</span>
- <span class="k">throw</span> <span class="k">new</span> <span class="nx">TypeError</span><span class="p">(</span>
- <span class="s1">'sum(): Both arguments must be numbers. Got: "'</span> <span class="o">+</span> <span class="nx">a</span> <span class="o">+</span> <span class="s1">'" and "'</span> <span class="o">+</span> <span class="nx">b</span> <span class="o">+</span> <span class="s1">'"'</span>
- <span class="p">);</span>
- <span class="p">}</span>
- <span class="k">return</span> <span class="nx">a</span> <span class="o">+</span> <span class="nx">b</span><span class="p">;</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>The key to failing fast is to <strong>make your assumptions explicit</strong> with assertions.</p> <p>The function above uses <a href="#type-checks-with-typeof"><code class="highlighter-rouge">typeof</code></a> to assert the types of <code class="highlighter-rouge">a</code> and <code class="highlighter-rouge">b</code>. It <a href="#programmatic-exceptions">throws an exception</a> if they are not numbers or if they are <code class="highlighter-rouge">NaN</code>. We are going to explain these techniques later in detail.</p> <p>This example shows that assertions make small errors visible before they grow into big errors. The problem is, NaN <em>is a dangerous beast</em>. NaN is a special value that means “not a number”, but in fact it is a number you can calculate with.</p> <p>NaN is contagious. All calculations involving NaN fail silently, yielding NaN: <code class="highlighter-rouge">5 + NaN</code> makes NaN, <code class="highlighter-rouge">Math.sqrt(NaN)</code> produces NaN. All comparisons with NaN yield false: <code class="highlighter-rouge">5 > NaN</code> is false, <code class="highlighter-rouge">5 < NaN</code> is also false. <code class="highlighter-rouge">5 === NaN</code> is false, <code class="highlighter-rouge">NaN === NaN</code> is also false.</p> <p>If a NaN slips into your logic, it is carried through the rest of the program until the user sees a “NaN” appearing in the interface. It is hard to find the cause of a NaN since the place where it appears can be far from the place that caused it. Typically, the cause of a NaN is an implicit type conversion. My advice is to raise the alarm as soon as you see a NaN.</p> <p>You need to decide how to implement assertions. If you throw an exception, like in the example above, make sure to catch it in a global error handler and <a href="#error-logging">report it to an error logging service</a>. If you follow <a href="#postels-law">Postel’s Law</a> instead, at least output a warning on the console and report the error.</p> <p>If the user’s task is affected, you should show a useful error message that something went wrong and that the incident has been reported. Also suggest workarounds, if applicable.</p> <h3 id="feature-detection">Feature detection</h3> <p><dfn>Feature detection</dfn> is a fundamental technique in an ever-changing web. As web authors, we want to use the newest browser features to provide a rich experience to the users and to make our life easier.</p> <p>Feature detection first checks whether a browser supports a certain web technology, then uses the technology safely. In the context of JavaScript, most feature detections are object and value checks, as well as function calls. Before looking at them in detail in the next chapter, let us learn about the basics of feature detection.</p> <p>When writing client-side JavaScript, you need to define a baseline of requirements. You need to take some basic features for granted, like ECMAScript 3 and W3C DOM Level 2 support. If you use other JavaScript features, you should first learn about the browser support.</p> <p><strong><a href="https://caniuse.com/">Can I Use</a></strong> is an essential resource that documents browser support of web technologies. For example, according to Can I Use, the <a href="https://caniuse.com/#feat=fetch">Fetch API</a> in available in the browsers of 77.81% of the users worldwide. Can I Use allows to import usage data for a certain country in order to see stats for the target market.</p> <p>The Can I Use data for Fetch shows that it is a fairly new API that almost all latest browsers support, but not the older browser generations. So Fetch should be used with a feature detection, ideally with a fallback or <a href="#polyfills">polyfill</a>.</p> <p>Another essential site is the <strong><a href="https://developer.mozilla.org/en-US/docs/Web/API">Web API documentation of the Mozilla Developer Network (MDN)</a></strong>. Here you will find a reference of all major JavaScript APIs, alongside with browser compatibility information and links to the original specifications.</p> <p>If you are looking for ECMAScript core features, the place to go are the <strong><a href="https://kangax.github.io/compat-table/es6/">ECMAScript compatibility tables by kangax</a></strong>.</p> <p>As we have learned before, writing good feature detection requires thorough knowledge of the particular JavaScript API you would like to use. Fortunately, people have developed and collected feature checks for the relevant APIs so you do not have to wade through the specifications and come up with proper checks yourself.</p> <p><strong><a href="https://modernizr.com/">Modernizr</a></strong> is a comprehensive feature detection library. You can select browser features you would like to use and build your own minimal library. Modernizr then provides a global object <code class="highlighter-rouge">Modernizr</code> with boolean properties. For example, <code class="highlighter-rouge">Modernizr.fetch</code> has the value <code class="highlighter-rouge">true</code> if the browser supports the Fetch API, or <code class="highlighter-rouge">false</code> if it does not. This allows you to write:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nx">Modernizr</span><span class="p">.</span><span class="nx">fetch</span><span class="p">)</span> <span class="p">{</span>
- <span class="cm">/<em> Call fetch() </em>/</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>If you do not want to use Modernizr but look for bulletproof feature detection code, look into Modernizr’s <a href="https://github.com/Modernizr/Modernizr/tree/master/feature-detects">repository of detects</a>. For detecting the Fetch API, the <a href="https://github.com/Modernizr/Modernizr/blob/master/feature-detects/network/fetch.js">Modernizr simple checks</a> <code class="highlighter-rouge">'fetch' in window</code>.</p> <h3 id="types-of-checks">Types of checks</h3> <p>Writing feature detections in JavaScript means checking for names and values defined by the host environment.</p> <p>There are three levels of checks:</p> <ol> <li> <p><strong>Existence check</strong>: Does a name exist?</p> <ul> <li>Either: Does an identifier exist in the scope chain? Ultimately, does an identifier exist in the global scope?</li> <li>Or: Does a property exist on a certain object?</li> </ul> </li> <li><strong>Type check</strong>: After resolving the name to a value, does the value has the expected type?</li> <li><strong>Value check</strong>: Does the value equals the expected value?</li> </ol> <p>This is a cascade of checks you can perform. From top to bottom, the checks get more specific. Typically, we need check the existence and the type of a value in order to use it safely. Sometimes checking the value is necessary as well.</p> <h3 id="conditional-statements-and-truthy-values">Conditional statements and truthy values</h3> <p>The key to robust JavaScript is <a href="https://christianheilmann.com/2015/02/18/progressive-enhancement-is-not-about-javascript-availability/">asking “if” a lot</a>. During the concept phase, ask “what if”. In the code, ask <code class="highlighter-rouge">if</code> to handle different cases differently.</p> <p>The <code class="highlighter-rouge">if</code> statement, or conditional statement, consists of a condition, a code block and an optional second code block.</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nx">condition</span><span class="p">)</span> <span class="p">{</span>
- <span class="c1">// …</span>
- <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
- <span class="c1">// …</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>When an <code class="highlighter-rouge">if</code> statement is evaluated, first the condition expression is evaluated. The result of the expression is then converted into a boolean value, <code class="highlighter-rouge">true</code> or <code class="highlighter-rouge">false</code>. If this result is <code class="highlighter-rouge">true</code>, the first code block is executed, otherwise the second block, if given.</p> <p>Most likely, this is not new to you. The reason I am revisiting it is the conversion into boolean. It means you can use a condition expression that does not necessarily evaluate to a boolean value. Other types, like Undefined, Null, String or Object are possible. For example, it is possible to write <code class="highlighter-rouge">if ("Hello!") {…}</code>.</p> <p>If you rely on the implicit conversion, you should learn the conversion rules. ECMAScript defines an <a href="http://www.ecma-international.org/ecma-262/8.0/#sec-toboolean">internal function ToBoolean</a> for this purpose. In our code, we can use the <a href="http://www.ecma-international.org/ecma-262/8.0/#sec-boolean-constructor-boolean-value">public <code class="highlighter-rouge">Boolean()</code> function</a> to convert a value into boolean. This delegates to the internal ToBoolean function.</p> <p>To illustrate the conversion, imagine that</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nx">condition</span><span class="p">)</span> <span class="p">{</span>
- <span class="c1">// …</span>
- <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
- <span class="c1">// …</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>is a short version of</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nb">Boolean</span><span class="p">(</span><span class="nx">condition</span><span class="p">)</span> <span class="o">===</span> <span class="kc">true</span><span class="p">)</span> <span class="p">{</span>
- <span class="c1">// …</span>
- <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
- <span class="c1">// …</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>Values are called <dfn>truthy</dfn> when ToBoolean converts them into <code class="highlighter-rouge">true</code>. Values are called <dfn>falsy</dfn> when ToBoolean converts them into <code class="highlighter-rouge">false</code>.</p> <p>The way ToBoolean works is simple, but with a twist. Let us quote the ECMAScript specification which is quite readable for once:</p> <table> <caption>ToBoolean Conversions</caption> <thead> <tr> <th>Argument Type</th> <th>Result</th> </tr> </thead> <tbody> <tr> <td>Undefined</td> <td>Return <strong>false</strong>.</td> </tr> <tr> <td>Null</td> <td>Return <strong>false</strong>.</td> </tr> <tr> <td>Boolean</td> <td>Return <em>argument</em>.</td> </tr> <tr> <td>Number</td> <td>If <em>argument</em> is <strong>+0</strong>, <strong>-0</strong>, or <strong>NaN</strong>, return <strong>false</strong>; otherwise return <strong>true</strong>.</td> </tr> <tr> <td>String</td> <td>If <em>argument</em> is the empty String (its length is zero), return <strong>false</strong>; otherwise return <strong>true</strong>.</td> </tr> <tr> <td>Symbol</td> <td>Return true.</td> </tr> <tr> <td>Object</td> <td>Return true.</td> </tr> </tbody> </table> <p>As you can see, most types have a clear boolean counterpart. All objects, including functions, dates, regular expressions and errors, are truthy. The two types denoting emptyness, <code class="highlighter-rouge">undefined</code> and <code class="highlighter-rouge">null</code>, are falsy.</p> <p>For numbers and strings though, it is complicated. Numbers are truthy <em>except for</em> zeros and NaN. Strings are truthy except for empty strings.</p> <p>This ECMAScript design decision is controversial. On the one hand, it is a source of errors, since some developers expect that all numbers and all strings are truthy. On the other hand, it allows to write simple value checks like <code class="highlighter-rouge">if (value) {…}</code> for non-empty strings and usable non-zero numbers.</p> <p>Usually, a value check aims to distinguish <em>usable</em> and <em>valid</em> values from <em>unusable</em> and <em>invalid</em> values. In most cases, truthy values are usable and falsy values are unusable. But keep in mind the exceptions for numbers and strings.</p> <p>If you choose not to use implicit type conversion, make sure the <code class="highlighter-rouge">if</code> condition directly evaluates to boolean. For example, use comparison operators like <code class="highlighter-rouge">===</code>, <code class="highlighter-rouge">!==</code>, <code class="highlighter-rouge">></code> and <code class="highlighter-rouge"><=</code>. These always produce boolean values.</p> <h3 id="existence-checks">Existence checks</h3> <p>Does an identifier exist in the scope chain? Ultimately, does an identifier exist in the global scope?</p> <p>Let us assume we would like to detect the Fetch API that specifies a global function <code class="highlighter-rouge">fetch</code>. Let us try this:</p> <div class="erroneous"> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nx">fetch</span><span class="p">)</span> <span class="p">{</span>
- <span class="nx">fetch</span><span class="p">(</span><span class="cm">/<em> … </em>/</span><span class="p">);</span>
- <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
- <span class="c1">// …</span>
- <span class="p">}</span>
- </code></pre></div> </div> </div> <p>This works in browsers that do support fetch, but throws an exception in browsers that do not. Especially, it throws a <a href="#reference-errors">ReferenceError</a>.</p> <p>This renders the whole check useless. This is exactly what we are trying to avoid with the check.</p> <p>We cannot just use an identifier that cannot be resolved. There are several ways to work around this problem:</p> <ol> <li> <p>We know that <code class="highlighter-rouge">fetch</code> is a property of the global object <code class="highlighter-rouge">window</code>. So we can use the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in"><code class="highlighter-rouge">in</code> operator</a> to check whether the property exists without checking its type:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="s1">'fetch'</span> <span class="k">in</span> <span class="nb">window</span><span class="p">)</span> <span class="p">{</span>
- <span class="nx">fetch</span><span class="p">(</span><span class="cm">/<em> … </em>/</span><span class="p">);</span>
- <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
- <span class="c1">// …</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>This existence check is in fact an object property check.</p> </li> <li> <p>If present, <code class="highlighter-rouge">fetch</code> is a property of <code class="highlighter-rouge">window</code> with the type Function. Knowing this, we access the property using the familiar dot notation, <code class="highlighter-rouge">object.property</code>:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">fetch</span><span class="p">)</span> <span class="p">{</span>
- <span class="nx">fetch</span><span class="p">(</span><span class="cm">/<em> … </em>/</span><span class="p">);</span>
- <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
- <span class="c1">// …</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>This existence check is in fact a <a href="#value-checks">value check</a>. We are relying on the <a href="#conditional-statements-and-truthy-values">ToBoolean conversion</a> here. A function is <em>truthy</em>.</p> </li> <li> <p>Alternatively, use the <a href="#type-checks-with-typeof"><code class="highlighter-rouge">typeof</code> operator</a>. <code class="highlighter-rouge">typeof</code> does not throw an error in case the identifier cannot be resolved, it merely returns the string <code class="highlighter-rouge">'undefined'</code>.</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">fetch</span> <span class="o">===</span> <span class="s1">'function'</span><span class="p">)</span> <span class="p">{</span>
- <span class="nx">fetch</span><span class="p">(</span><span class="cm">/<em> … </em>/</span><span class="p">);</span>
- <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
- <span class="c1">// …</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>This existence check is in fact a type check (see next chapter).</p> </li> </ol> <h3 id="type-checks-with-typeof">Type checks with <code class="highlighter-rouge">typeof</code></h3> <p><code class="highlighter-rouge">typeof</code> is an operator that takes one value as operand. The operator is placed before a value, for example <code class="highlighter-rouge">typeof 'Hello'</code>. As the name suggests, <code class="highlighter-rouge">typeof</code> returns the type of a value as a string. <code class="highlighter-rouge">typeof 'Hello'</code> evaluates to <code class="highlighter-rouge">'string'</code> since the value <code class="highlighter-rouge">'Hello'</code> is a string.</p> <p><code class="highlighter-rouge">typeof</code> has a behavior that makes it useful for feature detection: You can place an identifier after <code class="highlighter-rouge">typeof</code>, like <code class="highlighter-rouge">typeof fetch</code>. <code class="highlighter-rouge">typeof</code> does not throw an error in case the identifier cannot be resolved, it simply returns the string <code class="highlighter-rouge">'undefined'</code>.</p> <p>The problem is, <code class="highlighter-rouge">typeof</code> <em>is a dangerous beast</em>. <code class="highlighter-rouge">typeof</code> does not return what you probably expect. This operator is one of the biggest design flaws of ECMAScript. It is deceiving and in older browsers simply incorrect.</p> <p>First of all, let us learn about the <dfn>type system of ECMAScript</dfn>. There are seven main types: Undefined, Null, Boolean, Number, String, Symbol and Object. The first six are called <dfn>primitive types</dfn>.</p> <p>The seventh type, Object, has all sorts of subtypes: Function, Array, RegExp, Date, Error, Map, Set; Window, Document, Element, Node, Event, Image and much more. Values of these types are complex, made up of values of primitive types.</p> <p>You might expect that <code class="highlighter-rouge">typeof</code> deals with the seven main types by returning <code class="highlighter-rouge">'undefined'</code> for Undefined, <code class="highlighter-rouge">'null'</code> for Null, <code class="highlighter-rouge">'object'</code> for Object and so on. Unfortunately not.</p> <p>Let us paraphrase the ECMAScript specification to see <a href="http://www.ecma-international.org/ecma-262/8.0/#sec-typeof-operator">what typeof really returns</a>:</p> <table> <caption>typeof Operator Results</caption> <thead> <tr> <th>Operand type</th> <th>Result</th> </tr> </thead> <tbody> <tr> <td>Undefined</td> <td><code>"undefined"</code></td> </tr> <tr> <td>Null</td> <td><code>"object"</code></td> </tr> <tr> <td>Boolean</td> <td><code>"boolean"</code></td> </tr> <tr> <td>Number</td> <td><code>"number"</code></td> </tr> <tr> <td>String</td> <td><code>"string"</code></td> </tr> <tr> <td>Symbol</td> <td><code>"symbol"</code></td> </tr> <tr> <td>Object that is ordinary and not callable (not a function)</td> <td><code>"object"</code></td> </tr> <tr> <td>Object that is standard exotic and not callable (not a function)</td> <td><code>"object"</code></td> </tr> <tr> <td>Object that is ordinary and callable (a function)</td> <td><code>"function"</code></td> </tr> <tr> <td>Object that is non-standard exotic and not callable (not a function)</td> <td> <p>Implementation-defined, but not <code>'undefined'</code>, <code>'boolean'</code>, <code>'function'</code>, <code>'number'</code>, <code>'symbol'</code>, or <code>'string'</code>.</p> <p>Implementations are discouraged from defining new typeof result values for non-standard exotic objects. If possible <code>'object'</code> should be used for such objects.</p> </td> </tr> </tbody> </table> <p>The first oddity is that <code class="highlighter-rouge">typeof null</code> returns <code class="highlighter-rouge">object</code>, which does not make any sense. It is a dangerous pitfall.</p> <p>The second oddity is the special detection of functions. A function typically has the type Object, but <code class="highlighter-rouge">typeof</code> returns <code class="highlighter-rouge">'function'</code> instead of <code class="highlighter-rouge">'object'</code>. This exception turns out to be highly useful: <code class="highlighter-rouge">typeof</code> is the easiest way to detect a function. Unfortunately, there are no other exceptions for common object types. For arrays, dates and regular expressions, <code class="highlighter-rouge">typeof</code> still returns <code class="highlighter-rouge">'object'</code>.</p> <p>The third oddity is the distinction between ordinary, standard exotic and non-standard exotic objects. Let us try to understand this distinction without going too much into detail.</p> <p>An <dfn>ordinary object</dfn> comes with a default behavior that all objects share. An <dfn>exotic object</dfn> overrides and redefines the default behavior. Exotic objects are either <dfn>standard</dfn> (specified in ECMAScript) or <dfn>non-standard</dfn> (not specified in ECMAScript). For example, an array is a <dfn>standard exotic object</dfn>.</p> <p>In the past, browsers have provided objects that fall into the “non-standard exotic” category. The <code class="highlighter-rouge">typeof</code> operator in Internet Explorer misidentified these objects as <code class="highlighter-rouge">'unknown'</code>. Also Internet Explorer misidentified ordinary, callable objects (functions) as <code class="highlighter-rouge">'object'</code>.</p> <p>Newer browsers adhere to the specification, but the historical pitfalls remain. Since the result of <code class="highlighter-rouge">typeof</code> used to be unreliable, people have used <code class="highlighter-rouge">typeof</code> mostly for existence checks instead of explicit type checks.</p> <p>Let us look at the Fetch API example again:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">fetch</span> <span class="o">===</span> <span class="s1">'function'</span><span class="p">)</span> <span class="p">{</span>
- <span class="nx">fetch</span><span class="p">(</span><span class="cm">/<em> … </em>/</span><span class="p">);</span>
- <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
- <span class="c1">// …</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>This check uses <code class="highlighter-rouge">typeof</code> to assert the Function type. This is more explicit: Since we are going to call <code class="highlighter-rouge">fetch</code>, we assert it is a function.</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">fetch</span> <span class="o">!==</span> <span class="s1">'undefined'</span><span class="p">)</span> <span class="p">{</span>
- <span class="nx">fetch</span><span class="p">(</span><span class="cm">/<em> … </em>/</span><span class="p">);</span>
- <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
- <span class="c1">// …</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>This check uses <code class="highlighter-rouge">typeof</code> to assert <code class="highlighter-rouge">fetch</code> is defined and has an arbitrary type except Undefined. This is implicit: We assert <code class="highlighter-rouge">fetch</code> exists, then assume it is a function defined by the Fetch API.</p> <p>Both are useful feature checks. Personally, I follow the rule “explicit is better than implicit”.</p> <h3 id="type-checks-with-instanceof">Type checks with <code class="highlighter-rouge">instanceof</code></h3> <p>Besides <code class="highlighter-rouge">typeof</code>, there are several other ways to check the type of a value. One of them is the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof"><code class="highlighter-rouge">instanceof</code> operator</a>.</p> <p>Simply speaking, <code class="highlighter-rouge">instanceof</code> returns whether an object is an instance of a given class. For example, <code class="highlighter-rouge">value instanceof Date</code> returns true if the value is a <code class="highlighter-rouge">Date</code> object. <code class="highlighter-rouge">instanceof</code> expects the object on the left side and a class on the right side.</p> <p>More precisely, <code class="highlighter-rouge">instanceof</code> returns whether an object inherits from the <code class="highlighter-rouge">prototype</code> property of a given constructor. To understand this, we quickly need to revisit ECMAScript’s object model.</p> <p>ECMAScript is a language based on <dfn>prototypal inheritance</dfn>. Every object has a prototype reference that may point to another object. If a property cannot be found on the object, the JavaScript engine follows the prototype reference and looks for the property on the prototype.</p> <p>This principle is quite simple. Imagine someone asking you a question, but you do not know the answer. You still try to be helpful: “I’m sorry, I do not know the answer myself, but I know someone who is an expert on this topic!” So the other person walks to the expert and repeats the question.</p> <p>Since a prototype is a simple object, it can have its own prototype again. This way, a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain">prototype chain</a> is formed: Objects referencing other objects, like <code class="highlighter-rouge">a</code> → <code class="highlighter-rouge">b</code> → <code class="highlighter-rouge">c</code>. The engine walks up the prototype chain to find a property. When you retrieve a property on <code class="highlighter-rouge">a</code> and it cannot be found, <code class="highlighter-rouge">b</code> is searched, then <code class="highlighter-rouge">c</code>.</p> <p>How does <code class="highlighter-rouge">instanceof</code> fit in here? Let us investigate what happens when <code class="highlighter-rouge">value instanceof Date</code> is evaluated. <code class="highlighter-rouge">instanceof</code> expects a constructor on the right side, <code class="highlighter-rouge">Date</code> in the example. First the engine gets the <code class="highlighter-rouge">prototype</code> property of the constructor, <code class="highlighter-rouge">Date.prototype</code>. This is the prototype of all date objects. Then it takes the value on the left side, <code class="highlighter-rouge">value</code>, and walks up its prototype chain. If <code class="highlighter-rouge">Date.prototype</code> is found in the chain, the operator returns <code class="highlighter-rouge">true</code>, otherwise <code class="highlighter-rouge">false</code>.</p> <p>In consequence, <code class="highlighter-rouge">value instanceof Date</code> checks whether the value inherits from <code class="highlighter-rouge">Date.prototype</code> using prototypal inheritance.</p> <p>The <code class="highlighter-rouge">instanceof</code> operator is only applicable to the type Object and subtypes like Function, Array, RegExp, Date, etc. <code class="highlighter-rouge">instanceof</code> always returns false for <a href="#type-checks-with-typeof">primitive types</a>.</p> <p>Another drawback limits the usefulness of <code class="highlighter-rouge">instanceof</code>: It does not work across windows, like frames, iframes and popup windows.</p> <p>Every browser window has its own set of host objects and therefore constructor functions. For example, <code class="highlighter-rouge">Array</code> in one window is a different object than <code class="highlighter-rouge">Array</code> in another window. This sounds logical, but it causes problems when two windows exchange JavaScript objects.</p> <p>Assume there is one HTML document embedding another HTML document in an iframe. A script in the iframe document calls a function in the parent document, passing an array of numbers: <code class="highlighter-rouge">parent.reportFigures([ 63, 843, 13 ])</code>.</p> <p>The function <code class="highlighter-rouge">reportFigures</code> now wants to check if the argument is an array. Typically, <code class="highlighter-rouge">value instanceof Array</code> would be a good fit. But in this scenario, it is a <em>foreign</em> array that does not inherit from <code class="highlighter-rouge">Array.prototype</code> in the parent window. <code class="highlighter-rouge">value instanceof Array</code> would return <code class="highlighter-rouge">false</code> – a false negative.</p> <p>The standard way to solve this particular problem is to use a type check function provided by ECMAScript: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray">Array.isArray()</a>. Unfortunately, equivalents for other types like Date and RegExp do not exist.</p> <h3 id="duck-typing">Duck typing</h3> <p>As a weakly typed language, JavaScript performs implicit type conversion so developers do not need to think much about types. The concept behind this is called <dfn>duck typing</dfn>: “If it walks like a duck and quacks like a duck, it is a duck.”</p> <p><code class="highlighter-rouge">typeof</code> and <code class="highlighter-rouge">instanceof</code> check what a value <em>is</em> and <em>where it comes from</em>. As we have seen, both operators have serious limitations.</p> <p>In contrast, duck typing checks what a value <em>does</em> and <em>provides</em>. After all, you are not interested in the type of a value, you are interested in <em>what you can do with the value</em>.</p> <p>For example, a function that expects a date may check the input with <code class="highlighter-rouge">instanceof Date</code>:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">getNextDay</span><span class="p">(</span><span class="nx">date</span><span class="p">)</span> <span class="p">{</span>
- <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="nx">date</span> <span class="k">instanceof</span> <span class="nb">Date</span><span class="p">))</span> <span class="p">{</span>
- <span class="k">throw</span> <span class="k">new</span> <span class="nx">TypeError</span><span class="p">(</span><span class="s1">'getNextDay: expected a date'</span><span class="p">);</span>
- <span class="p">}</span>
- <span class="kd">const</span> <span class="nx">nextDay</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">();</span>
- <span class="nx">nextDay</span><span class="p">.</span><span class="nx">setTime</span><span class="p">(</span><span class="nx">date</span><span class="p">.</span><span class="nx">getTime</span><span class="p">());</span>
- <span class="nx">nextDay</span><span class="p">.</span><span class="nx">setDate</span><span class="p">(</span><span class="nx">nextDay</span><span class="p">.</span><span class="nx">getDate</span><span class="p">()</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
- <span class="k">return</span> <span class="nx">nextDay</span><span class="p">;</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>Duck typing would ask instead: What does the function do with the value? Then check whether the value fulfills the needs, and be done with it.</p> <p>The example function above calls the method <code class="highlighter-rouge">getTime</code> on the value. Why not accept all objects that have a <code class="highlighter-rouge">getTime</code> method?</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="nx">date</span> <span class="o">&&</span> <span class="k">typeof</span> <span class="nx">date</span><span class="p">.</span><span class="nx">getTime</span> <span class="o">===</span> <span class="s1">'function'</span><span class="p">))</span> <span class="p">{</span>
- <span class="k">throw</span> <span class="k">new</span> <span class="nx">TypeError</span><span class="p">(</span><span class="s1">'getNextDay: expected a date'</span><span class="p">);</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>If the value walks and talks like a date, it is a date – for this purpose.</p> <p>This check is not as strict as <code class="highlighter-rouge">instanceof</code>, and that is an advantage. A function that does not assert types but object capabilities is more flexible.</p> <p>For example, JavaScript has several types that do not inherit from <code class="highlighter-rouge">Array.prototype</code> but walk and talk like arrays: Arguments, HTMLCollection and NodeList. A function that uses duck typing is able to support all array-like types.</p> <h3 id="value-checks">Value checks</h3> <p>Compared to existence and type checks, value checks are less relevant for feature detection, but they are still important for writing robust application logic.</p> <p>We’ve learned that putting a value in an <code class="highlighter-rouge">if</code> condition makes a <em>truthy</em> test. When being converted to boolean, is the value <code class="highlighter-rouge">true</code>?</p> <p>The truthy test is simple and effective to determine if a value is usable, but it comes with several limitations we’ve already visited. For a lot of feature checks, the truthy test suffices. See the Fetch API example again:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">fetch</span><span class="p">)</span> <span class="p">{</span>
- <span class="nx">fetch</span><span class="p">(</span><span class="cm">/<em> … </em>/</span><span class="p">);</span>
- <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
- <span class="c1">// …</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>When detecting features, testing for a specific value is rare. Most feature detection looks for the existence objects and functions. There is no specific value to compare them to.</p> <p>In normal application logic though, testing for specific values is common. Such value checks make use of JavaScript’s comparison operators: <code class="highlighter-rouge"><</code>, <code class="highlighter-rouge">></code>, <code class="highlighter-rouge"><=</code>, <code class="highlighter-rouge">>=</code>, <code class="highlighter-rouge">==</code>, <code class="highlighter-rouge">!=</code>, <code class="highlighter-rouge">===</code> and <code class="highlighter-rouge">!==</code>.</p> <p>For example, you may want to check the length of an array or a string:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nx">array</span><span class="p">.</span><span class="nx">length</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/<em> … </em>/</span> <span class="p">}</span>
- <span class="k">if</span> <span class="p">(</span><span class="nx">string</span><span class="p">.</span><span class="nx">length</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/<em> … </em>/</span> <span class="p">}</span>
- </code></pre></div> </div> <p>Or if an array contains a given value:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nx">array</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="o">!==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/<em> … </em>/</span> <span class="p">}</span>
- </code></pre></div> </div> <p>Unfortunately, the comparison operators in JavaScripts <em>are dangerous beasts</em>. The relational operators like <code class="highlighter-rouge"><</code>, <code class="highlighter-rouge">></code>, <code class="highlighter-rouge"><=</code> and <code class="highlighter-rouge">>=</code> are overloaded with behavior so they work both for numbers and strings. They may implicitly convert the operands into numbers.</p> <p>The equality operators <code class="highlighter-rouge">==</code> and <code class="highlighter-rouge">!=</code> are even more complex. If the types of the operands do not match, they perform an implicit type conversion. We will not go into the details of ECMAScript’s equality comparison algorithm. For the sake of robustness, it is best practice to avoid these two operators altogether.</p> <p>Fortunately, the strict equality operators <code class="highlighter-rouge">===</code> and <code class="highlighter-rouge">!==</code> exist. They do not perform implicit type conversion. Hence they are easier to describe:</p> <p>The <code class="highlighter-rouge">===</code> operator first checks if the types of the operands match. If they do not, return <code class="highlighter-rouge">false</code>. This means you have to do manual type conversion if you want to compare values of different types.</p> <p>Then the operator checks if both operands are of the type Object. If they are, check if both are the same object. If yes return <code class="highlighter-rouge">true</code>, else return <code class="highlighter-rouge">false</code>. So two objects are considered unequal unless they are identical. There is no deep comparison of object properties.</p> <p>Otherwise, both operands must be of <a href="#type-checks-with-typeof">primitive types</a>. The values are compared directly. If they match, return <code class="highlighter-rouge">true</code>, else return <code class="highlighter-rouge">false</code>.</p> <p>These rules are not trivial and you still have to learn and remember them. The strict equality operators force you to think about types again. They make implicit logic explicit.</p> <h3 id="handling-exceptions-with-trycatch">Handling exceptions with try…catch</h3> <p>JavaScript APIs have different ways to report failure. The simplest way is a function that returns a falsy or empty value. For example, <code class="highlighter-rouge">document.querySelector('.peanutButter')</code> returns <code class="highlighter-rouge">null</code> if no element with the selector could be found in the document. Similarly, <code class="highlighter-rouge">document.querySelectorAll('.peanutButter')</code> returns an empty list if no element with the selector could be found.</p> <p>In addition to return values, APIs may throw exceptions. For example, <code class="highlighter-rouge">document.querySelector('!"§$%')</code> does not return <code class="highlighter-rouge">null</code>, but throws a SyntaxError: <code class="highlighter-rouge">'!"§$%' is not a valid selector</code>.</p> <p>You may have guessed that <code class="highlighter-rouge">!"§$%</code> is not a valid CSS selector, but browsers throw the same type of error when they do not recognize the selector. For example, older browsers like Internet Explorer 8 do support <code class="highlighter-rouge">querySelector</code>, but do not support <a href="https://caniuse.com/#feat=css-sel3">CSS Selectors Level 3</a>. And most recent browsers do not support the <a href="https://drafts.csswg.org/selectors-4/">CSS Selectors Level 4 Working Draft</a> yet.</p> <p>So a program that calls <code class="highlighter-rouge">querySelector</code> would need to check both:</p> <ol> <li>Is the return value an element and not <code class="highlighter-rouge">null</code>?</li> <li>Does the function throw an exception?</li> </ol> <p>We’ve learned how to check the return value:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">selector</span> <span class="o">=</span> <span class="s1">'a selector that might be unknown or invalid'</span><span class="p">;</span>
- <span class="kd">const</span> <span class="nx">element</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="nx">selector</span><span class="p">);</span>
- <span class="k">if</span> <span class="p">(</span><span class="nx">element</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
- <span class="c1">// … Do something with element …</span>
- <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
- <span class="c1">// … Error handling …</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>But how do we check whether the function threw an exception?</p> <p>The <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch"><code class="highlighter-rouge">try { … } catch (error) { … }</code> statement</a> wraps a piece of code and adds exception handling.</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">selector</span> <span class="o">=</span> <span class="s1">'a selector that might be unknown or invalid'</span><span class="p">;</span>
- <span class="kd">let</span> <span class="nx">element</span><span class="p">;</span>
- <span class="k">try</span> <span class="p">{</span>
- <span class="nx">element</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="nx">selector</span><span class="p">);</span>
- <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
- <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">error</span><span class="p">);</span>
- <span class="c1">// … Report the error to a logging service …</span>
- <span class="p">}</span>
- <span class="k">if</span> <span class="p">(</span><span class="nx">element</span><span class="p">)</span> <span class="p">{</span>
- <span class="c1">// … Do something with element …</span>
- <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
- <span class="c1">// No match. This might indicate an error as well.</span>
- <span class="c1">// Report the error to a logging service.</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>The <code class="highlighter-rouge">try…catch</code> statement consists of two main parts. The first part is the <code class="highlighter-rouge">try</code> block delimited by curly braces <code class="highlighter-rouge">{ … }</code>. This block contains the code that may throw an exception. The second part after the keyword <code class="highlighter-rouge">catch</code> consists of a variable name in parentheses, <code class="highlighter-rouge">(error)</code>, and the another code block in curly braces <code class="highlighter-rouge">{ … }</code>. This block is executed when an exception is thrown in the <code class="highlighter-rouge">try</code> block. It can access the error object using the name in the parentheses, <code class="highlighter-rouge">error</code> in the example.</p> <p>Normally, an exception stops the execution of the current function call stack. When the source of the exception is wrapped in <code class="highlighter-rouge">try…catch</code>, only the execution of the <code class="highlighter-rouge">try</code> block is stopped. Using error handling, the program is able to recover from the exception. The example above catches exceptions caused by <code class="highlighter-rouge">querySelector</code> and reports them.</p> <p>After executing the <code class="highlighter-rouge">catch (…) {…}</code> block, the JavaScript engine continues to run the code after the <code class="highlighter-rouge">try…catch</code> statement. There is an <code class="highlighter-rouge">if</code> statement with a truthy test on <code class="highlighter-rouge">element</code>. This covers two cases: <code class="highlighter-rouge">querySelector</code> returned <code class="highlighter-rouge">null</code> or threw an exception. If <code class="highlighter-rouge">querySelector</code> returned <code class="highlighter-rouge">null</code>, <code class="highlighter-rouge">element</code> is falsy. If <code class="highlighter-rouge">querySelector</code> threw an error, the assignment <code class="highlighter-rouge">element = …</code> never happened. Therefore <code class="highlighter-rouge">element</code> is <code class="highlighter-rouge">undefined</code>, also falsy.</p> <p><code class="highlighter-rouge">try…catch</code> is particularly useful when it wraps a small piece code that is likely to throw an error. Wrap an API call in <code class="highlighter-rouge">try…catch</code> when the API specification states that the call may throw exceptions.</p> <p><code class="highlighter-rouge">try…catch</code> is often misused by placing a large amount of code in the <code class="highlighter-rouge">try { … }</code> block. Often the <code class="highlighter-rouge">catch (…) {…}</code> block is left empty. This is not error handling, it is error suppression. It may be necessary in some cases, but <code class="highlighter-rouge">try…catch</code> is most useful for catching specific exceptions.</p> <h3 id="programmatic-exceptions">Programmatic exceptions</h3> <p>We’ve touched programmatic exceptions briefly in <a href="#failing-fast">“fail fast” assertions</a>. Let us have a deeper look at them.</p> <p>Typically we take great efforts to avoid or catch exceptions during JavaScript runtime. Why should we deliberately cause exceptions?</p> <p>Exceptions aren’t inherently good or bad. They are simply messages stating that something went wrong during execution. These messages can be very helpful given someone is listening to them and takes action.</p> <p>In the <code class="highlighter-rouge">querySelector</code> example above, <code class="highlighter-rouge">querySelector</code> uses two ways to send messages to the caller: A return value or an exception. In our own code, we can use the same pattern.</p> <p>Here is our “fail fast” example again, a function that returns a number or throws a <code class="highlighter-rouge">TypeError</code> if it cannot produce a number:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">sum</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="p">{</span>
- <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="k">typeof</span> <span class="nx">a</span> <span class="o">===</span> <span class="s1">'number'</span> <span class="o">&&</span> <span class="o">!</span><span class="nb">isNaN</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="o">&&</span>
- <span class="k">typeof</span> <span class="nx">b</span> <span class="o">===</span> <span class="s1">'number'</span> <span class="o">&&</span> <span class="o">!</span><span class="nb">isNaN</span><span class="p">(</span><span class="nx">b</span><span class="p">)))</span> <span class="p">{</span>
- <span class="k">throw</span> <span class="k">new</span> <span class="nx">TypeError</span><span class="p">(</span>
- <span class="s1">'sum(): Both arguments must be numbers. Got: "'</span> <span class="o">+</span> <span class="nx">a</span> <span class="o">+</span> <span class="s1">'" and "'</span> <span class="o">+</span> <span class="nx">b</span> <span class="o">+</span> <span class="s1">'"'</span>
- <span class="p">);</span>
- <span class="p">}</span>
- <span class="k">return</span> <span class="nx">a</span> <span class="o">+</span> <span class="nx">b</span><span class="p">;</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>The <code class="highlighter-rouge">throw</code> statement allows to throw an exception programmatically. It expects an arbitrary value after the <code class="highlighter-rouge">throw</code> keyword, but <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error">Error objects</a> and their subtypes are most useful.</p> <p>The example creates a new <code class="highlighter-rouge">TypeError</code> instance. Every Error should have a meaningful message describing the problem. The message is a string that is passed to the Error constructor.</p> <p>First, a programmatic exception is a message to the developer calling the code. The <code class="highlighter-rouge">sum</code> function says: “This function needs two numbers in order to work correctly! It does not deal with other types. For reliability, this function does not perform implicit type conversion. Please fix your code to make sure only numbers are passed, before this small error grows to a big one.”</p> <p>This message is only effective if it reaches the developer. When the exception is thrown in production, then it should be <a href="#error-logging">reported and logged</a> so the developer gets the message as soon as possible.</p> <p>Second, a programmatic exception is a message to the calling code, similar to the return value of the function. We’ve seen this in the <code class="highlighter-rouge">querySelector</code> example above. The caller should catch the exception and handle it appropriately. For this purpose, the error object holds a type, a message, the source code position it originates from, a stack trace and possibly more information on the incident.</p> <h3 id="the-strict-mode">The Strict Mode</h3> <p>ECMAScript 5, released in 2009, started to deprecate error-prone programming practices. But it could not just change code semantics from one day to the next. This would have broken most existing code.</p> <p>In order to maintain backwards compatibility, ECMAScript 5 introduces the <dfn>Strict Mode</dfn> as an opt-in feature. In Strict Mode, common pitfalls are removed from the language or throw visible exceptions. Previously, several programming mistakes and bogus code were ignored silently. The Strict Mode turns these mistakes into visible errors – see <a href="#failing-fast">failing fast</a>.</p> <p>Enable the Strict Mode by placing a marker at the beginning of a script:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s1">'use strict'</span><span class="p">;</span>
- <span class="nb">window</span><span class="p">.</span><span class="nx">alert</span><span class="p">(</span><span class="s1">'This code is evaluated in Strict Mode! Be careful!'</span><span class="p">);</span>
- </code></pre></div> </div> <p>Or at the beginning of a function:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">strictFunction</span><span class="p">()</span> <span class="p">{</span>
- <span class="s1">'use strict'</span><span class="p">;</span>
- <span class="nb">window</span><span class="p">.</span><span class="nx">alert</span><span class="p">(</span><span class="s1">'This function is evaluated in Strict Mode! Be careful!'</span><span class="p">);</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>Syntax-wise, <code class="highlighter-rouge">'use strict';</code> is simply an expression statement with a string literal. This code does not do anything when evaluated. It is a meaningful marker for browsers that support ECMAScript 5, and innocuous code for browsers that do not.</p> <p>Enabling the Strict Mode for a script or a function is contagious. All code syntactically nested also switches to Strict Mode. For example:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">window</span><span class="p">.</span><span class="nx">alert</span><span class="p">(</span><span class="s1">'Non-strict mode!'</span><span class="p">);</span></p>
- <p><span class="kd">function</span> <span class="nx">strictFunction</span><span class="p">()</span> <span class="p">{</span>
- <span class="s1">'use strict'</span><span class="p">;</span></p>
- <p><span class="nb">window</span><span class="p">.</span><span class="nx">alert</span><span class="p">(</span><span class="s1">'Strict Mode!'</span><span class="p">);</span>
- <span class="nx">nestedFunction</span><span class="p">();</span></p>
- <p><span class="kd">function</span> <span class="nx">nestedFunction</span><span class="p">()</span> <span class="p">{</span>
- <span class="nb">window</span><span class="p">.</span><span class="nx">alert</span><span class="p">(</span><span class="s1">'Strict Mode as well!'</span><span class="p">);</span>
- <span class="p">}</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>The Strict Mode changes a lot of small things that you can <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode">read about elsewhere</a>. A big thing is the handling of variable assignments in functions.</p> <p>Consider this function:</p> <div class="erroneous"> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">sloppyFunction</span><span class="p">()</span> <span class="p">{</span>
- <span class="nx">name</span> <span class="o">=</span> <span class="s1">'Alice'</span><span class="p">;</span>
- <span class="nb">window</span><span class="p">.</span><span class="nx">alert</span><span class="p">(</span><span class="nx">name</span><span class="p">);</span>
- <span class="p">}</span>
- </code></pre></div> </div> </div> <p>In non-strict mode, the assignment to <code class="highlighter-rouge">name</code> implicitly creates a global variable, <code class="highlighter-rouge">window.name</code>. Coincidentally, <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/name">window.name</a> already exists and has a special meaning.</p> <p><code class="highlighter-rouge">name</code> is not supposed to be a global variable here, but a local variable in the scope of <code class="highlighter-rouge">sloppyFunction</code>. We forgot to add <code class="highlighter-rouge">var</code>, <code class="highlighter-rouge">let</code> or <code class="highlighter-rouge">const</code> before the assignment.</p> <p>In Strict Mode, this mistake does not go unnoticed. It leads to a <a href="#reference-errors">ReferenceError</a>: “assignment to undeclared variable name”.</p> <p>Here is the fixed code that is also valid in Strict Mode:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">strictFunction</span><span class="p">()</span> <span class="p">{</span>
- <span class="s1">'use strict'</span><span class="p">;</span>
- <span class="kd">var</span> <span class="nx">name</span> <span class="o">=</span> <span class="s1">'Alice'</span><span class="p">;</span>
- <span class="nb">window</span><span class="p">.</span><span class="nx">alert</span><span class="p">(</span><span class="nx">name</span><span class="p">);</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>Today, the Strict Mode should be used everywhere unless there are particular reasons against it.</p> <p>Newer ECMAScript versions make the Strict Mode the default when using new features. For example, ECMAScript 6 module code is always evaluated in Strict Mode. Code inside of ECMAScript 6 classes is also Strict Mode per default.</p> <p>Most likely, if you are using modules or classes, you are already using the Strict Mode. If not, I highly recommend to use the <code class="highlighter-rouge">'use strict';</code> marker in your scripts to enable the Strict Mode.</p> <h3 id="abstraction-libraries">Abstraction libraries</h3> <p><a href="https://jquery.com/">jQuery</a>, <a href="http://underscorejs.org/">Underscore</a>, <a href="https://lodash.com/">Lodash</a> and <a href="https://momentjs.com/">Moment.js</a> are probably the most used client-side JavaScript libraries. They all emerged for two main reasons:</p> <ol> <li>The JavaScript APIs available in the browser were unhandy, clumsy and lacked essential features or expressiveness.</li> <li>The web lacked technical standards that browser vendors agreed upon. Or old browsers that lacked support for essential web standards still dominated the market.</li> </ol> <p>A main goal of client-side JavaScript libraries is to even out differences between browsers. Back in the beginnings of the web, these differences were enormous. Today, most JavaScript APIs are well-specified and browser vendors care for interoperability. Still, small differences remain. Even after browsers have fixed bugs in their implementations, old browser versions do not simply vanish into thin air but delight us for years.</p> <p>Every year or so, someone writes an article titled “You do not need jQuery” or “You do not need Lodash”. These articles point out that the native APIs have been improved since or old browsers that prevented the usage of native APIs have died out. That is right, but they often miss the other main goal of libraries.</p> <p>Libraries provide a concise and consistent API that is an abstraction of several inconsistent browser APIs. For example, using jQuery for traversing and manipulating the DOM, handling events and animation is still more pleasant than using the respective native APIs. This is because jQuery provides an unbeaten abstraction: A list type containing DOM nodes with powerful map, reduce and filter operations. Also, jQuery still deals with browser inconsistencies and tries to level them.</p> <p>For the sake of robustness, use well-tested, rock-solid libraries. The time, resources and brain power that went into the creation and maintenance of such libraries do not compare to your own solutions.</p> <h3 id="polyfills">Polyfills</h3> <p>Polyfills are an important tool for writing robust, cross-browser JavaScript. A polyfill is a script that fixes holes in the browser’s web standard support in order to create a level playing field for other scripts. It implements a particular JavaScript API in case the browser does not support it natively yet.</p> <p>Polyfills are like libraries, but instead of defining their own API, they implement an established or emerging web standard. The benefit for the developer is that after loading the polyfill, all browsers provide the same feature with the same standard API.</p> <p>For example, some browsers do not support the Fetch API. A <a href="https://github.com/github/fetch">polyfill for the Fetch API</a> implements the Fetch specification using older existing techniques like <code class="highlighter-rouge">XMLHttpRequest</code>. Then it fills the browser’s holes.</p> <p>A polyfill for the Fetch API may have the following structure:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">window</span><span class="p">.</span><span class="nx">fetch</span><span class="p">)</span> <span class="p">{</span>
- <span class="nb">window</span><span class="p">.</span><span class="nx">fetch</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
- <span class="cm">/<em> … Polyfill code … </em>/</span>
- <span class="p">};</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>If the browser does not support the Fetch API, including <code class="highlighter-rouge">window.fetch</code>, the code fills the hole by defining <code class="highlighter-rouge">window.fetch</code>.</p> <p>It is worth noting that not all APIs can be fully polyfilled. Some APIs include new and special behavior that cannot be implemented by standard ECMAScript means.</p> <p>For example, if the browser does not provide access to live audio and video streams from the device, no JavaScript polyfill can implement this feature. In such cases, you need to use Graceful Degradation or <a href="#progressive-enhancement">Progressive Enhancement</a> to come up with an alternative.</p> <h3 id="linters">Linters</h3> <p>A linter is a program that checks code for potential errors and compatibility issues. Some linters also enforce style guide rules, like code formatting and naming conventions. Typically a linter has a command line interface, but it can also be integrated in most editors and build tools.</p> <p>When developing JavaScript, a linter is an essential tool for writing robust code. If you take one thing away from this guide, let it be the use of a linter. It will point out most issues that are described here and much more.</p> <p>The most flexible and powerful JavaScript linter is <strong><a href="https://eslint.org/">ESlint</a></strong>. It is written in JavaScript and runs on Node.js.</p> <p>ESlint consists of an ECMAScript <a href="#parsing-errors">parser</a> and <a href="https://eslint.org/docs/rules/">numerous rules</a> that examine the Abstract Syntax Tree (AST). The rules search for pitfalls in your code, for deprecated language idioms, for inconsistencies and code style violations.</p> <p>When a rule finds a violation, it outputs a warning or an error you can see on the command line or in your editor. Some rules, especially stylistic rules, may automatically fix the problem by changing the source file.</p> <p>In addition to the built-in rules, ESlint is fully extensible via plugins. A lot of libraries and ecosystems have created ESlint plugins that check for the respective best practices. For example, there are <a href="https://github.com/yannickcr/eslint-plugin-react">rules for writing React.js code</a> and <a href="https://github.com/evcohen/eslint-plugin-jsx-a11y">rules that check React/JSX code for accessibility issues</a>.</p> <p>Since more and more markup and style logic on the web is expressed in JavaScript, it is crucial to check the JavaScript code for well-established best practices from these domains as well.</p> <p>Before ESlint existed, JavaScript best practices were described in books, blog posts, talks and project style guides. But not all of them could be checked and enforced automatically. ESlint became a tool for documenting best practices as well as checking them.</p> <p>ESlint continues to shape the way people write JavaScript. Large projects and corporations are sharing their ESlint configurations. For example, the <a href="https://github.com/airbnb/javascript">AirBnB style guide</a> and the <a href="https://standardjs.com/">“Standard” style</a> are popular style guides based on ESlint.</p> <p>ESlint is a safe and easy way to explore different programming paradigms possible with JavaScript. With ESlint, it is possible to impose strict rules on your JavaScript usage. For example, <a href="https://github.com/jfmengels/eslint-plugin-fp">ESLint rules for functional programming</a> disallow all JavaScript features that contradict the concepts of pure functional programming.</p> <p>Especially for beginners, the ESlint ecosystem may be confusing. Hundreds of rules with configuration options, hundreds of plugins and conflicting guidelines. There are few things people quarrel about more than the “right” programming style.</p> <p>Fortunately, ESlint and most ESlint plugins come with a recommended configuration. Start with this configuration to get an impression how ESlint works, then adapt your ESlint configuration to reflect your or your team’s preferences.</p> <h3 id="the-babel-compiler">The Babel compiler</h3> <p>Every year, a new ECMAScript version is released. Some versions introduce new syntax. For example, ECMAScript 6 (released 2015) introduced a bunch of new syntax features. Here is a small selection:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">a</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
- <span class="kd">const</span> <span class="nx">b</span> <span class="o">=</span> <span class="mb">0b1100101</span><span class="p">;</span>
- <span class="kd">const</span> <span class="nx">c</span> <span class="o">=</span> <span class="s2"><code>Hello </span><span class="p">${</span><span class="nx">a</span><span class="p">}</span><span class="s2"></code></span><span class="p">;</span>
- <span class="kd">const</span> <span class="nx">func</span> <span class="o">=</span> <span class="p">(</span><span class="nx">x</span><span class="p">)</span> <span class="o">=></span> <span class="nx">x</span> <span class="o">*</span> <span class="mi">2</span><span class="p">;</span>
- <span class="kd">class</span> <span class="nx">Cat</span> <span class="p">{}</span>
- </code></pre></div> </div> <p>The ECMAScript syntax is not forward-compatible. When new syntax is added, engines that do not support the extension cannot parse the code. They throw a <a href="#parsing-errors">SyntaxError</a> and do not execute the code.</p> <p>There are still browsers around that do not support the ECMAScript 6 syntax. Does this mean we cannot use ECMAScript 6 until all these browsers become extinct?</p> <p>We can use the newest language features today, but we need to translate the code to an older version of ECMAScript before shipping it to the browsers.</p> <p>The <a href="https://babeljs.io/">Babel compiler</a> makes this possible. It turns new syntax into an older, more compatible syntax. For example, Babel may translate ECMAScript 6 syntax to equivalent ECMAScript 5 code. A compiler that is in fact a translator is called <dfn>transpiler</dfn>, because nerds love blending words.</p> <p>When using Babel, there is a mandatory compilation step between hand-written code and the code delivered to browsers. Babel provides a command line tool for translating JavaScript files. It also <a href="https://babeljs.io/docs/setup/">integrates well</a> with popular build tools like <a href="https://gruntjs.com">Grunt</a>, <a href="https://gulpjs.com">Gulp</a>, <a href="http://browserify.org">Browserify</a> and <a href="https://webpack.js.org">Webpack</a>.</p> <p>Babel is not just a tool to transform code written in a newer ECMAScript version into an older version. Babel is a plugin-based parser and translation framework that may support arbitrary syntax extensions.</p> <p>As you can imagine, this is both powerful and dangerous. On the one hand, people use Babel to prototype and test new <a href="https://github.com/tc39/proposals">ECMAScript language proposals</a>. On the other hand, people use Babel to add syntax that most likely will not be standardized, like <a href="https://reactjs.org/docs/introducing-jsx.html">JSX</a>.</p> <p>This leads to a situation where large codebases are not valid ECMAScript but full of syntax extensions. Some of them are on the standards track, some are not. Such code can only be parsed by Babel with certain plugins. Like in the biblical story about the <a href="https://en.wikipedia.org/wiki/Tower_of_Babel">Tower of Babel</a>, language confusion prevents people from working together.</p> <p>The safest approach is to write interoperable code conforming to a released ECMAScript specification and compile it to an older version using Babel.</p> <p>A core assumption of compiling new syntax into old syntax is that a fully equivalent old syntax exists at all. This is not always the case. Some new ECMAScript 6 features cannot be fully translated into ECMAScript 5. Babel does its best to reproduce the semantics, but keep in mind that some detailed behavior cannot be reproduced.</p> <p>These difference are not noticeable if you ship the same ECMAScript 5 code to all browsers. But in the future it makes sense to ship a smaller build with ECMAScript 6 to the browsers that support it.</p> <p>Babel primarily deals with syntax extensions, not with extensions to the standard library, the ECMAScript core objects. For example, if you write:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">pElements</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s1">'p'</span><span class="p">));</span>
- </code></pre></div> </div> <p>Babel will it translate to:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s1">'use strict'</span><span class="p">;</span>
- <span class="kd">var</span> <span class="nx">pElements</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s1">'p'</span><span class="p">));</span>
- </code></pre></div> </div> <p>Still, this will not work in a browser that only supports ECMAScript 5 because <code class="highlighter-rouge">Array.from()</code> is first specified in ECMAScript 6. Babel does not translate the function call. To use new ECMAScript objects and methods, you can use a <a href="#polyfills">polyfill</a>. The Babel project provides a <a href="https://babeljs.io/docs/usage/polyfill/">polyfill based on core-js</a>.</p> <h3 id="languages-that-compile-to-javascript">Languages that compile to JavaScript</h3> <p>From a language design point of view, JavaScript has severe shortcomings and pitfalls. The technical term is “footgun”: A technology that makes it easy to shoot yourself in the foot.</p> <p>JavaScript is weakly and dynamically typed. It borrows ideas from multiple paradigms and fuses them into one language: imperative programming, object-oriented programming and functional programming.</p> <p>Some people make this imprecision and inconsistency responsible for many JavaScript pitfalls. That is true to some regard. JavaScript was not originally designed for writing large web applications or user interfaces. Recent ECMAScript standards introduced more strictness and consistency to improve <a href="https://en.wikipedia.org/wiki/Programming_in_the_large_and_programming_in_the_small">programming in the large</a>.</p> <p>When it is so hard to write robust JavaScript, why not use another programming language?</p> <p>The browsers only have a JavaScript engine built in, so we cannot just run, say, PHP in the browser. But other languages can be translated into JavaScript, like an Arabic text can be translated into English.</p> <p>Typically, programming languages are compiled into machine code for a specific processor architecture or into bytecode for a virtual machine. It is also possible to compile them into another language, like JavaScript. As we’ve learned already, such a compiler-translator is called transpiler.</p> <p>Transpilers allow to write front-end code in an arbitrary language. Someone has to develop the transpiler, of course. This opens up tremendous possibilities. We can use strictly-typed languages, or purely functional languages, or languages designed for the purpose of building user interfaces.</p> <p>In fact, there are numerous languages that compile to JavaScript. They have different levels of familiarity with JavaScript:</p> <ul> <li>Some languages are strict subsets of JavaScript, meaning they resemble JavaScript in all points but remove some problematic aspects.</li> <li>Some languages are strict supersets of JavaScript, meaning they resemble JavaScript in all points and add additional features.</li> <li>Some languages have a different, incompatible syntax that resembles JavaScript.</li> <li>Some languages have a different, incompatible syntax that does not resemble JavaScript.</li> </ul> <p>Let us have a look at a small selection of languages that compile to JavaScript.</p> <h4 id="coffeescript">CoffeeScript</h4> <p><a href="http://coffeescript.org/">CoffeeScript</a> was one of the first widely-used languages that compile to JavaScript. It has a syntax very familiar to JavaScript. CoffeeScript’s motto is “It is just JavaScript”. It mostly provides “syntactic sugar” that makes writing common JavaScript idioms easier.</p> <p>In JavaScript, curly braces <code class="highlighter-rouge">{ … }</code> are used to delimit functions and blocks. In CoffeeScript, whitespace like line breaks and spaces is used for that purpose. The mapping from CoffeeScript to JavaScript is direct. The compiled JavaScript code closely resembles the CoffeeScript source.</p> <p>Here is the <code class="highlighter-rouge">sum</code> function in CoffeeScript:</p> <p>When CoffeeScript version 1.0 was released on 2010, it made JavaScript programming more robust since it eliminated several common pitfalls. The JavaScript produced by the CoffeeScript compiler implemented best practices and was less error-prone.</p> <p>CoffeeScript’s language design and its brevity influenced the work on the ECMAScript standard. ECMAScript 6 and <a href="#the-babel-compiler">Babel</a> address several language shortcomings that existed when CoffeeScript was created. So today CoffeeScript is used less then it was several years ago, but it is still an influential language.</p> <h4 id="typescript">TypeScript</h4> <p><a href="https://www.typescriptlang.org/">TypeScript</a> is an ambitious effort by Microsoft to create a language that compiles to JavaScript by extending standard ECMAScript.</p> <p>As the name suggest, TypeScript adds static typing to JavaScript. It comes with well-known ways to define types, like classes, interfaces, unions and generics.</p> <p>TypeScript is a strict superset of ECMAScript. All valid ECMAScript code is also valid TypeScript, but TypeScript code with type annotations is usually not valid ECMAScript.</p> <p>This design decision makes learning and adopting TypeScript easier. You do not have to forget everything you know about JavaScript and learn a new language, you focus on learning additional TypeScript features.</p> <p>This is how the <code class="highlighter-rouge">sum</code> function with explicit type annotations may look like in TypeScript:</p><pre>
- function sum(a: number, b: number): number {
- return a + b;
- }
- </pre><p>Do you see the type information added to the parameters <code class="highlighter-rouge">a</code> and <code class="highlighter-rouge">b</code> as well as the return value?</p> <p>In plain JavaScript, we need to add <a href="#type-checks-with-typeof">type assertions</a> to make sure that <code class="highlighter-rouge">sum</code> is only called with two numbers. In TypeScript, the code simply does not compile when <code class="highlighter-rouge">sum</code> is called somewhere with non-numbers. By adding type information, the TypeScript compiler can analyze the code and check if the actual type of a value matches the expected type.</p> <p>Using a language with strong, static typing like TypeScript has these main benefits:</p> <ul> <li>With proper typings in place, the compiler catches a certain class of bugs early. It is harder to write code that fails for simple reasons. Runtime errors like TypeError and ReferenceError are almost eliminated.</li> <li>Static typing forces you to handle cases that are logically possible, even though they are rare in practice. Without type checking, someone has to write <a href="#automated-testing">automated tests</a> for the edge cases, otherwise the errors are not caught.</li> <li>Static typing makes you think twice about the structure of your data, about object modeling and API design. In plain JavaScript code, it is easy to create, mix and mutate complex objects. This makes it hard to see which properties are available and which types they have. In TypeScript, each function has a well-defined signature. The structure of all objects passed around in the code is described by classes or interfaces.</li> <li>Strong typing means there is no implicit type conversion. Explicit code is simpler code.</li> <li>Editors with strong TypeScript support, like Visual Studio Code, make programming a bliss. They have productivity features known from fully fledged <abbr title="Integrated development environments">IDEs</abbr>. Writing, navigating and refactoring code is much easier since the editor understands the structure of the program, knows all names and types.</li> </ul> <p>But what are the downsides?</p> <ul> <li>Although TypeScript is a superset of ECMAScript, learning TypeScript thoroughly takes a lot of effort. Especially for people who have not worked with statically typed languages before, the type system is fundamentally new and hard to grasp.</li> <li>Turning JavaScript into a type-safe language is not easy. The TypeScript compiler knows the semantics of all ECMAScript operators and built-in types. In addition, there are <a href="http://definitelytyped.org/">type definitions for browser APIs and libraries</a>. Since the code still runs in loosely-typed JavaScript land, the type definitions do not always match the reality.</li> <li>TypeScript may give a false sense of safety. TypeScript aims for type safety on <em>compile time</em> given that all code has correct type definitions. After the trans bvlation to JavaScript, all type information is discarded. Dynamic code can still create errors during <em>runtime</em>. So runtime checks are still necessary and valuable.</li> <li>Like other compile-to-JavaScript languages, writing TypeScript requires setting up the compiler. To enjoy all benefits, you need to use a specific editor and <a href="https://github.com/palantir/tslint">linter</a>.</li> </ul> <p>In conclusion, TypeScript is a valuable tool to make JavaScript programming more robust.</p> <h4 id="clojurescript">ClojureScript</h4> <p><a href="https://clojurescript.org/">ClojureScript</a> is a compile-to-JavaScript language derived from <a href="https://clojure.org/">Clojure</a>, an independent, well-established language. It embraces functional programming with optional type safety. It has a Lisp-like syntax that follows a “code is data” philosophy. Clojure code is typically compiled to bytecode running on the Java virtual machine.</p> <p>Clojure and ClojureScript share little resemblance with JavaScript and the ties to the JavaScript ecosystem are loose. Both the unfamiliar Lisp-like syntax and the functional programming style may put off JavaScript developers.</p> <p>Here is how the contrived <code class="highlighter-rouge">sum</code> function looks in ClojureScript:</p> <div class="language-clojure highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">sum</span><span class="w"> </span><span class="p">[</span><span class="n">a,</span><span class="w"> </span><span class="n">b</span><span class="p">]</span><span class="w">
- </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">b</span><span class="p">))</span><span class="w">
- </span></code></pre></div> </div> <p>This is how to call the function and output the result using JavaScript’s premium debugging tool, <code class="highlighter-rouge">console.log()</code>:</p> <div class="language-clojure highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nf">js/console.log</span><span class="w"> </span><span class="p">(</span><span class="nf">sum</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">2</span><span class="p">))</span><span class="w">
- </span></code></pre></div> </div> <p>The core philosophy of Clojure is that it aims to be <a href="https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/SimpleMadeEasy.md"><em>simple</em> in the first place and <em>easy</em> in the second place</a>. Let us unravel that profound sentence.</p> <p>“Simple” is the opposite of complex. “Simple” means having only one purpose, doing one thing. “Simple” means unambiguous and logically clear.</p> <p>“Easy” means familiar, easy to reach. “Easy” is the opposite of “hard”.</p> <p>Clojure tries to use “simple” concepts to build large applications. In other words, Clojure tries everything not to be a “footgun”.</p> <p>JavaScript in contrast is a melting pot of conflated features. New syntax and semantics are added with each version. JavaScript is “easy”, because its idioms are familiar to developers from different backgrounds.</p> <p>ClojureScript and functional programming in general keep influencing the way people write JavaScript and the way ECMAScript is advanced. In particular, two basic concepts: <dfn>pure functions</dfn> and <dfn>immutable values</dfn>.</p> <p>In short, a function is <dfn>pure</dfn> when it is free of <dfn>side effects</dfn>. Such a function takes some input values as arguments and computes a new value. The new value becomes the return value.</p> <p>A pure function always produces the same output given the same input. There is no internal state. The function does not change its input values. It does not do anything besides computing the return value. It does not “change the world”. So it is always safe to call a pure function: It may take some computation time, but it does not change the state of your application.</p> <p>It turned out that breaking down the logic of an application into pure functions makes the whole application more robust. Pure functions are “simple”, they do only one thing. They are easy to reason about and easy to test automatically. You can simply pass different input values and check the return value.</p> <p>Here is an example of an <em>impure</em> function in Javascript:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">myCats</span> <span class="o">=</span> <span class="p">[];</span></p>
- <p><span class="kd">function</span> <span class="nx">adoptCat</span><span class="p">(</span><span class="nx">cat</span><span class="p">)</span> <span class="p">{</span>
- <span class="c1">// Mutate the outer value myCats</span>
- <span class="nx">myCats</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">cat</span><span class="p">);</span>
- <span class="c1">// Mutate an input value</span>
- <span class="nx">cat</span><span class="p">.</span><span class="nx">owner</span> <span class="o">=</span> <span class="s1">'Alice'</span><span class="p">;</span>
- <span class="c1">// Output as a side effect, no return value</span>
- <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'Cat adopted!'</span><span class="p">,</span> <span class="nx">cat</span><span class="p">);</span>
- <span class="p">}</span></p>
- <p><span class="nx">adoptCat</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="s1">'Cheshire Cat'</span> <span class="p">});</span>
- </code></pre></div> </div> <p>Here is an example of a <em>pure</em> function in JavaScript:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">adoptCat</span><span class="p">(</span><span class="nx">cats</span><span class="p">,</span> <span class="nx">cat</span><span class="p">)</span> <span class="p">{</span>
- <span class="c1">// Make a copy of the cat object in order to augment it</span>
- <span class="kd">const</span> <span class="nx">adoptedCat</span> <span class="o">=</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="nx">cat</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="na">owner</span><span class="p">:</span> <span class="s1">'Alice'</span> <span class="p">};</span>
- <span class="c1">// Make a copy of the cats array in order to augment it</span>
- <span class="k">return</span> <span class="nx">cats</span><span class="p">.</span><span class="nx">concat</span><span class="p">(</span><span class="nx">adoptedCat</span><span class="p">);</span>
- <span class="p">}</span></p>
- <p><span class="kd">const</span> <span class="nx">myOldCats</span> <span class="o">=</span> <span class="p">[];</span>
- <span class="kd">const</span> <span class="nx">myNewCats</span> <span class="o">=</span> <span class="nx">adoptCat</span><span class="p">(</span><span class="nx">myOldCats</span><span class="p">,</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="s1">'Cheshire Cat'</span> <span class="p">});</span>
- <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'Cat adopted!'</span><span class="p">,</span> <span class="nx">myNewCats</span><span class="p">[</span><span class="nx">myNewCats</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]);</span>
- </code></pre></div> </div> <p>The rule that pure functions do not mutate their input values is enforced in functional programming languages like Clojure. Variable bindings as well as values are typically <em>immutable</em>: You can read them to create new values, but you cannot change them in-place.</p> <h4 id="elm">Elm</h4> <p>Elm is a functional programming language that compiles to JavaScript. In contrast to Clojure/ClojureScript it was specifically designed as a compile-to-JavaScript language. It is not a general-purpose programming language, it is a toolkit for developing client-side web applications.</p> <p>The syntax of Elm does not resemble JavaScript, but it is quite approachable. Here is how the familiar <code class="highlighter-rouge">sum</code> function looks like in Elm with explicit type annotations:</p><pre><code class="language-elm">sum : Float -> Float -> Float
- sum x y =
- x + y
- </code></pre><p>This code calls <code class="highlighter-rouge">sum</code> and outputs the result to the HTML document:</p><pre><code class="language-elm">main =
- text (
- toString (sum 1 2)
- )
- </code></pre><p>Elm’s main goal is to prevent runtime exceptions. In this guide, we’ve learned how hard that is in JavaScript when done manually. Elm’s idea is to free the developer from this burden. If the program may throw exceptions during runtime, it simply should not compile. The Elm compiler is known for its strictness as well as for friendly error messages that help you making the code compile.</p> <p>In Elm, operations still may fail. Like other functional languages, Elm has built-in types for wrapping uncertain values (<code class="highlighter-rouge">Maybe</code>: <code class="highlighter-rouge">Nothing</code> & <code class="highlighter-rouge">Just</code>) and operations that produce a value or an error (<code class="highlighter-rouge">Result</code>: <code class="highlighter-rouge">Ok</code> & <code class="highlighter-rouge">Error</code>). When working with potential values, the success and error cases must be handled explicitly.</p> <p>Elm is designed with static types from the ground up. Static typing feels natural since Elm has strong type inference. It deduces the types of values so you do not have to add type annotations in many places.</p> <p>TypeScript has type inference as well, but TypeScript imposes static typing on a dynamic language and ecosystem. Elm makes a clear cut.</p> <p>The real novelty of Elm is the “Elm Architecture”. As mentioned earlier, Elm is not a general-purpose language, but designed for building user interfaces running in the browser.</p> <p>Typically, such interfaces are built using patterns like <a href="https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller">Model View Controller</a> (MVC) or <a href="https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel">Model View ViewModel</a> (MVVM). These patterns originate from object-oriented languages that mix logic and mutable state. They are not applicable to functional languages with immutable values.</p> <p>In Elm, a dynamic user interface consists of three parts:</p> <ul> <li><dfn>Model</dfn> – A type describing the state of the application, as well as the initial value. This is where all work data is stored. There is no logic here.</li> <li><dfn>Update</dfn> – A pure function that takes a message and the existing model, processes the message and returns a new model. This is where all state changes happen, but in an immutable way.</li> <li><dfn>View</dfn> – A pure function that takes the model and returns the description of an HTML element tree. This is similar to a declarative HTML template. The view may embed information from the model in the HTML, for rendering data, and may register messages as event handlers, for adding interactivity.</li> </ul> <p>The update cycle in Elm looks like this:</p> <ol> <li>On user input, a message is sent.</li> <li>The <code class="highlighter-rouge">update</code> function is called automatically. It may react to the message and may return a new, updated state.</li> <li>The <code class="highlighter-rouge">view</code> function is called automatically with the updated state. It generates a new HTML element tree.</li> <li>Using a technique called Virtual DOM diffing, the actual DOM is updated.</li> </ol> <p>This concept is radically simple and radically different from classical UI patterns. It was later dubbed <dfn>uni-directional data flow</dfn> to contrast it with bi-directional model-view binding.</p> <p>In additional to synchronous model updates, messages can have asynchronous effects. The <code class="highlighter-rouge">update</code> function can return commands that trigger new messages eventually, like sending HTTP requests. The application may declare subscriptions for listening to input streams like WebSockets.</p> <p>Elm exists in a niche, but its brilliant concepts have been widely adopted in the larger JavaScript ecosystem. Elm influenced <a href="https://reactjs.org/">React</a>, <a href="https://facebook.github.io/flux/">Flux</a>, <a href="https://redux.js.org/">Redux</a> and <a href="https://github.com/ngrx/platform">NgRx</a> as well as several side-effect solutions for Redux.</p> <p>Even if you do not choose to write Elm over plain JavaScript, there is much to learn from Elm regarding robust programming.</p> <h3 id="error-logging">Error logging</h3> <p>Despite all precautions, with extensive <a href="#automated-testing">testing</a> in place, errors will happen in production when diverse users with diverse browsers and devices are using your site.</p> <p>In particular, JavaScript exceptions will happen in production. We’ve learned that exceptions are helpful messages about problems in your code or your larger infrastructure – as long as you receive these messages and act upon them.</p> <p>Therefore, sending information about exceptions to you, the developer, is vital for every site that relies on JavaScript.</p> <p>The standard approach is to monitor all exceptions on a page and to handle them in a central handler, for example using <a href="https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror">window.onerror</a>. Then gather a bunch of context information and send an incident report to a log server. That server stores all reports, makes them accessible using an interface and probably sends an email to the developer.</p> <p>Here is a simple global error reporter:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">window</span><span class="p">.</span><span class="nx">onerror</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">message</span><span class="p">,</span> <span class="nx">file</span><span class="p">,</span> <span class="nx">line</span><span class="p">,</span> <span class="nx">column</span><span class="p">,</span> <span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
- <span class="kd">var</span> <span class="nx">errorToReport</span> <span class="o">=</span> <span class="p">{</span>
- <span class="na">type</span><span class="p">:</span> <span class="nx">error</span> <span class="p">?</span> <span class="nx">error</span><span class="p">.</span><span class="nx">type</span> <span class="p">:</span> <span class="s1">''</span><span class="p">,</span>
- <span class="na">message</span><span class="p">:</span> <span class="nx">message</span><span class="p">,</span>
- <span class="na">file</span><span class="p">:</span> <span class="nx">file</span><span class="p">,</span>
- <span class="na">line</span><span class="p">:</span> <span class="nx">line</span><span class="p">,</span>
- <span class="na">column</span><span class="p">:</span> <span class="nx">column</span><span class="p">,</span>
- <span class="na">stack</span><span class="p">:</span> <span class="nx">error</span> <span class="p">?</span> <span class="nx">error</span><span class="p">.</span><span class="nx">stack</span> <span class="p">:</span> <span class="s1">''</span><span class="p">,</span>
- <span class="na">userAgent</span><span class="p">:</span> <span class="nb">navigator</span><span class="p">.</span><span class="nx">userAgent</span><span class="p">,</span>
- <span class="na">href</span><span class="p">:</span> <span class="nx">location</span><span class="p">.</span><span class="nx">href</span>
- <span class="p">};</span>
- <span class="kd">var</span> <span class="nx">url</span> <span class="o">=</span> <span class="s1">'/error-reporting?error='</span> <span class="o">+</span>
- <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">errorToReport</span><span class="p">);</span>
- <span class="kd">var</span> <span class="nx">image</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Image</span><span class="p">();</span>
- <span class="nx">image</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span> <span class="nx">url</span><span class="p">;</span>
- <span class="p">};</span>
- </code></pre></div> </div> <p>This code sends a report to the address <code class="highlighter-rouge">/error-reporting</code> using a GET request.</p> <p>The example above is not enough. It is not that easy to compile a meaningful, cross-browser report from an exception. Tools like <a href="https://github.com/csnover/TraceKit">TraceKit</a> and <a href="https://www.stacktracejs.com/">StackTrace.js</a> help to extract meaning from exceptions.</p> <p>There are <a href="https://github.com/cheeaun/javascript-error-logging">several hosted services</a> that provide such error reporting scripts and the server-side monitoring and data processing.</p> <h3 id="manual-testing">Manual testing</h3> <p>Once a feature of a website is implemented, it needs to be tested manually. The first tester is typically the developer, switching between code editor and browser, adding logic and making the necessary input to test the logic. Before committing the code, there is probably a final test.</p> <p>These ad hoc tests do not scale. They are not exhaustive, not documented, not repeatable and they do not catch regressions. A regression is when a change in one part breaks another part. For example, improving one feature may accidentally break another feature.</p> <p>More importantly, the developer perspective is not as meaningful as the user perspective.</p> <p>There are several effective testing approaches in different development phases. To improve the robustness of client-side JavaScript, I find it most important to add frequent manual testing to the product development cycle.</p> <p>Typically, designing an application feature produces a <a href="https://en.wikipedia.org/wiki/User_story">user story</a> that describes who wants what and why: “As a new user, I want to browse restaurants nearby so I can find a cozy place for dinner”.</p> <p>The next step may be to design the user interaction that achieves the goal stated in the user story. For example, the home page contains a search form and asks for the user’s location. On form submission, a list of restaurants is shown.</p> <p>Every feature has a list of steps the user needs to perform. The feature and step descriptions are poured into a <dfn>test plan</dfn>.</p> <p>A dedicated tester now executes all tasks that a real user needs to be able to do. The tester verifies that all features are working as described and verifies that the application reacts to input as expected. If the tester encounters a mismatch or finds anything suspicious, they report a bug.</p> <p>Whenever the feature set changes or the code changes, the test plan needs to be revised and all tests need to be repeated in order to yield meaningful results.</p> <p>For websites, the tester needs to execute the tasks with different browsers, devices and internet connections to catch all possible errors.</p> <p>Manual testing with step-by-step instructions is probably the most time-consuming and expensive type of testing, but it is highly beneficial. Alongside with real user testing, manual testing can quickly find errors caused by client-side JavaScript.s.</p> <h3 id="automated-testing">Automated testing</h3> <p>Automated testing plays a crucial role in writing robust applications. Especially when writing JavaScript code, a simple automated test already catches a lot of common bugs.</p> <p>There are plenty of resources on automated testing in general and testing JavaScript in particular. In this guide, I will focus on how automated testing contributes to robust JavaScript.</p> <p>In contrast to <a href="#manual-testing">manual testing</a>, automated testing verifies that the software meets the requirements using automated means. This typically includes writing test code or another formal proof. Once the automated test is set up, it can be executed repeatedly without human interference.</p> <h3 id="unit-tests">Unit tests</h3> <p>A unit test is an automated test with the smallest possible scope that looks into the application code. Almost always, a unit test is code written in the same language as the implementation.</p> <p>In JavaScript, the smallest reusable unit of code is a function. Other possible units are an object, a class or a module. For example, a unit test for a JavaScript function is some JavaScript code that calls the function.</p> <p>For simplicity, let us write a unit test that deals with a function. But how do we write and execute a test?</p> <p>There are numerous ways how to write and run unit tests in JavaScript. Popular testing frameworks include <a href="https://jasmine.github.io/">Jasmine</a> and <a href="https://mochajs.org/">Mocha</a>. They may be combined with assertion libraries like <a href="http://chaijs.com/">Chai</a> and <a href="http://unexpected.js.org/">Unexpected</a>. Unit tests are typically executed using test runners like <a href="https://facebook.github.io/jest/">Jest</a>, <a href="https://github.com/avajs/ava">Ava</a> and <a href="https://karma-runner.github.io/1.0/index.html">Karma</a>.</p> <p>In my experience, all these libraries allow to write unit tests that make JavaScript more robust. It is mostly a matter of style and taste which one to use. For the purpose of this guide, I will use widely accepted Jasmine testing framework.</p> <p>First of all, we need a function to test. Let us start with the simple, flawed <code class="highlighter-rouge">sum</code> function:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">sum</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="p">{</span>
- <span class="k">return</span> <span class="nx">a</span> <span class="o">+</span> <span class="nx">b</span><span class="p">;</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>How would a unit test for this function look like and how does it make the code more robust?</p> <p>In Jasmine, a single unit test is called test suite. It <em>describes</em> the unit under test. The suite consists of <dfn>specifications</dfn> or shortly <dfn>specs</dfn>. Each spec sets up the necessary environment, pokes the unit under test and finally makes some <dfn>expectations</dfn>, also called <dfn>assertions</dfn>. If all expectations are met, the spec passes, otherwise the spec fails.</p> <p>Here is a simple Jasmine test suite for the <code class="highlighter-rouge">sum</code> function:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">describe</span><span class="p">(</span><span class="s1">'sum'</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
- <span class="nx">it</span><span class="p">(</span><span class="s1">'adds two numbers'</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
- <span class="nx">expect</span><span class="p">(</span><span class="nx">sum</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">)).</span><span class="nx">toBe</span><span class="p">(</span><span class="mi">4</span><span class="p">);</span>
- <span class="p">});</span>
- <span class="p">});</span>
- </code></pre></div> </div> <p><code class="highlighter-rouge">describe('…', function() {…});</code> declares a test suite. The first argument, <code class="highlighter-rouge">'sum'</code> in the example, is a description of the unit under test. The function passed to <code class="highlighter-rouge">describe</code> may contain several specs.</p> <p>A spec is declared with <code class="highlighter-rouge">it('…', function() {…})</code>. The first argument, <code class="highlighter-rouge">'adds two numbers'</code> in the example, is the human-readable requirement. The function passed to <code class="highlighter-rouge">it</code> contains the actual test code.</p> <p>The example above tests the <code class="highlighter-rouge">sum</code> function. Each spec needs to call the function with some arguments and make assertions about the return value. The example calls <code class="highlighter-rouge">sum(1, 3)</code> and expects the result to be 4 using Jasmine’s <code class="highlighter-rouge">expect</code> and <code class="highlighter-rouge">toBe</code> functions. As you can see, Jasmine code tries to be human-readable.</p> <p>If you do not understand the details of the code above, that is fine. It is rather important to understand the structure: A unit test describes the behavior of a piece of code and thereby documents the requirements. The unit test tells you whether the implementation meets the specifications.</p> <p>A specification consists of a human-readable text and an executable proof. A spec allows you to describe and verify how the code behaves in particular cases.</p> <p>Unit testing makes you think about these cases in the first place, then write them down and define behavior. Does the function return the correct result when valid input is given? How does the function behave when invalid input is given? Handling these cases makes the implementation more robust.</p> <p>As we’ve seen before, the simple <code class="highlighter-rouge">sum</code> function does not behave well when invalid input is given. Let us specify how the function <em>should behave</em> in this case. We want <code class="highlighter-rouge">sum</code> to throw an exception in case one argument is not a number.</p> <p>Here is the respective spec:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">it</span><span class="p">(</span><span class="s1">'throws an error if one argument is not a number'</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
- <span class="nx">expect</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
- <span class="nx">sum</span><span class="p">(</span><span class="s1">'1'</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span>
- <span class="p">}).</span><span class="nx">toThrow</span><span class="p">();</span>
- <span class="p">});</span>
- </code></pre></div> </div> <p>This spec fails when being tested against the implementation <code class="highlighter-rouge">function sum(a, b) { return a + b; }</code>. It is common practice to write a failing spec first. <dfn>Test-driven development</dfn> advises to first define the cases, specify the behavior and then write as little code as necessary to make the test pass.</p> <p>Let us do that! Here is the <code class="highlighter-rouge">sum</code> function that makes the test pass:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">sum</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="p">{</span>
- <span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">a</span> <span class="o">!==</span> <span class="s1">'number'</span><span class="p">)</span> <span class="p">{</span>
- <span class="k">throw</span> <span class="k">new</span> <span class="nx">TypeError</span><span class="p">(</span>
- <span class="s1">'sum(): Both arguments must be numbers. Got: "'</span> <span class="o">+</span> <span class="nx">a</span> <span class="o">+</span> <span class="s1">'" and "'</span> <span class="o">+</span> <span class="nx">b</span> <span class="o">+</span> <span class="s1">'"'</span>
- <span class="p">);</span>
- <span class="p">}</span>
- <span class="k">return</span> <span class="nx">a</span> <span class="o">+</span> <span class="nx">b</span><span class="p">;</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>Wait, is not something missing there? The code only checks the <code class="highlighter-rouge">a</code> argument. Should we not add a check for <code class="highlighter-rouge">b</code> as well?</p> <p>Yes, but first we write a failing spec:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">it</span><span class="p">(</span><span class="s1">'throws an error if one argument is not a number'</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
- <span class="nx">expect</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">sum</span><span class="p">(</span><span class="s1">'1'</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span> <span class="p">}).</span><span class="nx">toThrow</span><span class="p">();</span>
- <span class="nx">expect</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">sum</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s1">'3'</span><span class="p">);</span> <span class="p">}).</span><span class="nx">toThrow</span><span class="p">();</span>
- <span class="nx">expect</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">sum</span><span class="p">({},</span> <span class="kc">null</span><span class="p">);</span> <span class="p">}).</span><span class="nx">toThrow</span><span class="p">();</span>
- <span class="p">});</span>
- </code></pre></div> </div> <p>Now let us change the implementation so the test passes:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">sum</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="p">{</span>
- <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="k">typeof</span> <span class="nx">a</span> <span class="o">===</span> <span class="s1">'number'</span> <span class="o">&&</span> <span class="k">typeof</span> <span class="nx">b</span> <span class="o">===</span> <span class="s1">'number'</span><span class="p">))</span> <span class="p">{</span>
- <span class="k">throw</span> <span class="k">new</span> <span class="nx">TypeError</span><span class="p">(</span>
- <span class="s1">'sum(): Both arguments must be numbers. Got: "'</span> <span class="o">+</span> <span class="nx">a</span> <span class="o">+</span> <span class="s1">'" and "'</span> <span class="o">+</span> <span class="nx">b</span> <span class="o">+</span> <span class="s1">'"'</span>
- <span class="p">);</span>
- <span class="p">}</span>
- <span class="k">return</span> <span class="nx">a</span> <span class="o">+</span> <span class="nx">b</span><span class="p">;</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>One thing is still missing: The handling of <code class="highlighter-rouge">NaN</code> values. Let us add a failing spec:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">it</span><span class="p">(</span><span class="s1">'throws an error if one argument is NaN'</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
- <span class="nx">expect</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">sum</span><span class="p">(</span><span class="kc">NaN</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span> <span class="p">}).</span><span class="nx">toThrow</span><span class="p">();</span>
- <span class="nx">expect</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">sum</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="kc">NaN</span><span class="p">);</span> <span class="p">}).</span><span class="nx">toThrow</span><span class="p">();</span>
- <span class="nx">expect</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">sum</span><span class="p">(</span><span class="kc">NaN</span><span class="p">,</span> <span class="kc">NaN</span><span class="p">);</span> <span class="p">}).</span><span class="nx">toThrow</span><span class="p">();</span>
- <span class="p">});</span>
- </code></pre></div> </div> <p>Finally, this is the implementation that conforms to all specifications:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">sum</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="p">{</span>
- <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="k">typeof</span> <span class="nx">a</span> <span class="o">===</span> <span class="s1">'number'</span> <span class="o">&&</span> <span class="o">!</span><span class="nb">isNaN</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="o">&&</span>
- <span class="k">typeof</span> <span class="nx">b</span> <span class="o">===</span> <span class="s1">'number'</span> <span class="o">&&</span> <span class="o">!</span><span class="nb">isNaN</span><span class="p">(</span><span class="nx">b</span><span class="p">)))</span> <span class="p">{</span>
- <span class="k">throw</span> <span class="k">new</span> <span class="nx">TypeError</span><span class="p">(</span>
- <span class="s1">'sum(): Both arguments must be numbers. Got: "'</span> <span class="o">+</span> <span class="nx">a</span> <span class="o">+</span> <span class="s1">'" and "'</span> <span class="o">+</span> <span class="nx">b</span> <span class="o">+</span> <span class="s1">'"'</span>
- <span class="p">);</span>
- <span class="p">}</span>
- <span class="k">return</span> <span class="nx">a</span> <span class="o">+</span> <span class="nx">b</span><span class="p">;</span>
- <span class="p">}</span>
- </code></pre></div> </div> <p>Writing unit tests has several benefits. For one, it leads to a programming style that creates small units of code that are easy to test in isolation and have a well-defined interface. Moreover, it makes you think about robust code. By writing specs for ordinary as well as unusual conditions, by running the specs in several browsers, you put the assumptions baked into your code to the test.</p> <p>In commercial web applications that make heavy use of JavaScript, every line of JavaScript code should be covered by unit tests. Tools like <a href="https://istanbul.js.org/">Istanbul</a> allow to measure the test coverage.</p> <p>100% test coverage means there is no logic that is not executed by unit tests. It does not necessarily mean that the logic is correct. Writing meaningful specs that reflect the actual conditions in production requires a lot of experience.</p> <p>If you do not use unit testing yet, start small by writing specs for your core parts. It gives you a feeling about how testable code looks like and how to test different cases effectively.</p> <h3 id="integration-tests">Integration tests</h3> <p>As we’ve learned, unit testing tries to focus on a small reusable unit of code and to put it through its paces. A unit test assures that a unit works well in isolation.</p> <p>Such a test is precise, but it is hard to isolate a unit from the rest. For example, if a function under test calls a second function, the unit tests needs to remove and replace this dependency in order to focus on the function under test. A common technique is called <dfn>dependency injection</dfn>.</p> <p>Unit tests are necessary, but not sufficient. An application is a complex combination of units. Having 100% test coverage, having passing unit tests says little about the application as a whole.</p> <p>This is where <dfn>integration tests</dfn> come in. An integration test describes and verifies the behavior of a several connected units. The integration test does not need to know the internals, it runs against the public interface.</p> <p>For example, if a function under test calls a second function, the integration test simply lets it be. The test knows that it <em>integrates</em> all dependencies. Such a test has a larger impact and covers a lot of code. But it is hard to set up the different cases and test side effects thoroughly.</p> <p>When testing JavaScript, the difference between unit tests and integration tests is subtle. Most things we’ve learned about unit tests also apply to integration tests. For example, integration tests may use the same tools like Jasmine. In practice, unit tests and integration tests are mixed in order to test a codebase precisely and extensively.</p> <h3 id="acceptance-tests">Acceptance tests</h3> <p>Both unit and integration tests consist of code that checks various internal parts of the application code. Again, these tests say little about the application as a whole. The crucial question is whether the application works for the user. Is a user able to complete their tasks?</p> <p>A certain class of JavaScript bugs only occurs when the code runs on the target website in a real browser. These bugs are not caught by unit or integration tests running in a cleanroom environment that bears little resemblance to the production environment.</p> <p>JavaScript is error-prone because it depends on other front-end and back-end technologies. A script typically reads and changes the HTML DOM, changes CSS styles, makes HTTP requests and controls media. So when the script runs in production, it needs to work together with the HTML, CSS, other JavaScript code, server APIs and media content.</p> <p>We need an automated test that checks the website as a whole. This is called <dfn>acceptance testing</dfn> or <dfn>end-to-end testing</dfn> in the web context.</p> <p>An acceptance test does not tests parts of the application individually, like the front-end, back-end code or database, but the full stack. It ensures that all technologies come together to provide the desired user experience.</p> <p>In particular, an acceptance test simulates a user by remotely controlling a browser. Such a test mimics the input of a user and checks the output of the website. Every test consists of step-by-step instructions like these:</p> <ol> <li>Go to the website http://carols.example.org</li> <li>Wait until the page is fully loaded.</li> <li>Expect that the top-level heading reads “Christmas Carols”.</li> <li>Focus the search field by clicking on it.</li> <li>Enter the text “hark”.</li> <li>Submit the form by pressing enter.</li> <li>Wait until the next page is fully loaded.</li> <li>Expect that the top-level heading reads “Hark! The Herald Angels Sing”.</li> <li>Expect that the first paragraph contains “Peace on earth and mercy mild”.</li> </ol> <p>An acceptance test expresses these instructions as code. Since the test interacts with the website through a browser, it can be written in any language. It does not need to be JavaScript or whatever language is used in the back-end.</p> <p>Of course, you can write acceptance tests in JavaScript and run them with Node.js. Popular libraries include <a href="http://nightwatchjs.org/">Nightwatch.js</a> and <a href="http://webdriver.io/">WebdriverIO</a>.</p> <p>The technology that makes remote control of the browser possible is called <a href="https://www.w3.org/TR/webdriver/">WebDriver</a>. Today, all big browsers implement the WebDriver protocol. A popular server for orchestrating browsers is <a href="http://www.seleniumhq.org/">Selenium</a>.</p> <p>Like all types of testing, acceptance tests should run on different devices and browsers for meaningful results. Commercial services like <a href="https://saucelabs.com/">Saucelabs</a> and <a href="https://www.browserstack.com/">BrowserStack</a> allow to run WebDriver tests against numerous devices and browsers.</p> <h3 id="writing-less-javascript">Writing less JavaScript</h3> <p>The role of a front-end developer is to improve the user experience with the available technologies. The developer needs to assess which interactions can and should be improved with client-side JavaScript.</p> <p>JavaScript allows to build more user-friendly interfaces than HTML and CSS alone can do. It is the best technology to build excellent interactivity in the browser.</p> <p>Still, JavaScript is the most brittle of all front-end web technologies. An important skill of a front-end developer is to know when <em>not</em> to solve a problem with client-side JavaScript. It is always more robust to solve a problem further down in the stack.</p> <p>If all techniques and tools did not help you to write robust JavaScript, consider reducing the code complexity and the amount of code. In the last resort, reduce the usage of client-side JavaScript. Find simpler solutions that rely on HTML, CSS and server-side logic alone.</p> <p class="separator">❡</p> <h2 id="references">References</h2> <p class="separator">❡</p> <h2 id="about">About</h2> <p>Author: <a href="/">Mathias Schäfer (molily)</a></p> <p>Twitter: <a href="https://twitter.com/molily">@molily</a></p> <p>Please send feedback and corrections to <a href="mailto:zapperlott@gmail.com">zapperlott@gmail.com</a>.</p> <p>License: <a rel="license" href="https://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike (CC BY-SA 4.0)</a></p> <p>Published on <time datetime="2017-12-21">December 21st, 2017</time>.</p> <p><a href="/impressum/" lang="de" hreflang="de">Impressum</a></p> </main></p>
- </article>
- </section>
-
-
- <nav id="jumpto">
- <p>
- <a href="/david/blog/">Accueil du blog</a> |
- <a href="https://molily.de/robust-javascript/">Source originale</a> |
- <a href="/david/stream/2019/">Accueil du flux</a>
- </p>
- </nav>
-
- <footer>
- <div>
- <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
- <p>
- Bonjour/Hi!
- Je suis <a href="/david/" title="Profil public">David Larlet</a>, je vis actuellement à Montréal et j’alimente cet espace depuis 15 ans. <br>
- Si tu as apprécié cette lecture, n’hésite pas à poursuivre ton exploration. Par exemple via les <a href="/david/blog/" title="Expériences bienveillantes">réflexions bimestrielles</a>, la <a href="/david/stream/2019/" title="Pensées (dés)articulées">veille hebdomadaire</a> ou en t’abonnant au <a href="/david/log/" title="S’abonner aux publications via RSS">flux RSS</a> (<a href="/david/blog/2019/flux-rss/" title="Tiens c’est quoi un flux RSS ?">so 2005</a>).
- </p>
- <p>
- Je m’intéresse à la place que je peux avoir dans ce monde. En tant qu’humain, en tant que membre d’une famille et en tant qu’associé d’une coopérative. De temps en temps, je fais aussi des <a href="https://github.com/davidbgk" title="Principalement sur Github mais aussi ailleurs">trucs techniques</a>. Et encore plus rarement, <a href="/david/talks/" title="En ce moment je laisse plutôt la place aux autres">j’en parle</a>.
- </p>
-
- <p>
- Voici quelques articles choisis :
- <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
- <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
- <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
- <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
- <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
- <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
- <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
- <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
- <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
- <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
- <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
- <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
- <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
- <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
- <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
- et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
- </p>
- <p>
- On peut <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">échanger par courriel</a>. Si éventuellement tu souhaites que l’on travaille ensemble, tu devrais commencer par consulter le <a href="http://larlet.com">profil dédié à mon activité professionnelle</a> et/ou contacter directement <a href="http://scopyleft.fr/">scopyleft</a>, la <abbr title="Société coopérative et participative">SCOP</abbr> dont je fais partie depuis six ans. Je recommande au préalable de lire <a href="/david/blog/2018/cout-site/" title="Attention ce qui va suivre peut vous choquer">combien coûte un site</a> et pourquoi je suis plutôt favorable à une <a href="/david/pro/devis/" title="Discutons-en !">non-demande de devis</a>.
- </p>
- <p>
- Je ne traque pas ta navigation mais mon
- <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
- conserve des logs d’accès.
- </p>
- </div>
- </footer>
- <script type="text/javascript">
- ;(_ => {
- const jumper = document.getElementById('jumper')
- jumper.addEventListener('click', e => {
- e.preventDefault()
- const anchor = e.target.getAttribute('href')
- const targetEl = document.getElementById(anchor.substring(1))
- targetEl.scrollIntoView({behavior: 'smooth'})
- })
- })()
- </script>
|