<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Erranto RSS Feed</title><description>Elvis Adomnica writes about programming, software engineering, and web development.</description><link>https://erranto.com</link><item><title>The try/catch/maybe story</title><link>https://erranto.com/blog/try-catch-maybe</link><guid isPermaLink="true">https://erranto.com/blog/try-catch-maybe</guid><pubDate>Sun, 02 Mar 2025 16:55:09 GMT</pubDate><content:encoded>&lt;p&gt;import JSSnippet from &quot;../../components/JSSnippet.svelte&quot;&lt;/p&gt;
&lt;p&gt;When we lean about exception handling, and the &lt;code&gt;try/catch/finally&lt;/code&gt; statements, we are
told that the &lt;code&gt;finally&lt;/code&gt; block is guaranteed to always execute. Here is an example:&lt;/p&gt;
&lt;p&gt;&amp;lt;JSSnippet client:load&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function doSomething() {

}

try {
  doSomething();
  console.log(&quot;Done!&quot;);
  return 42;
} catch (e) {
  console.error(&quot;Got error:&quot;, e);
} finally {
  console.log(&quot;Finally!&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/JSSnippet&amp;gt;&lt;/p&gt;
&lt;p&gt;The code above will successfully execute &lt;code&gt;doSomething&lt;/code&gt; (the function doesn&apos;t do anything
at the moment), it will print &lt;code&gt;Done&lt;/code&gt;, and then, even though there is a &lt;code&gt;return&lt;/code&gt; statement,
it will print &lt;code&gt;Finally!&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let&apos;s see what happens if we throw an exception inside &lt;code&gt;doSomething&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&amp;lt;JSSnippet client:load&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function doSomething() {
  throw new Error(&quot;Nope!&quot;);
}

try {
  doSomething();
  console.log(&quot;Done!&quot;);
  return 42;
} catch (e) {
  console.error(&quot;Got error:&quot;, e);
} finally {
  console.log(&quot;Finally!&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/JSSnippet&amp;gt;&lt;/p&gt;
&lt;p&gt;As expected, the exception being thrown will interrupt the execution of the
&lt;code&gt;try&lt;/code&gt; block, triggering the &lt;code&gt;catch&lt;/code&gt; block, which will print &lt;code&gt;Got error: Error: Nope!&lt;/code&gt;.
After that, the &lt;code&gt;finally&lt;/code&gt; block will execute, printing &lt;code&gt;Finally!&lt;/code&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;For synchronous code the &lt;code&gt;finally&lt;/code&gt; guarantee holds true. The question is, will it also
hold true for async code? Technically yes, but with a caveat. The promise that is being
awaited needs to be resolved or rejected before the rest of the code can execute.&lt;/p&gt;
&lt;p&gt;Let&apos;s return to our code examples and this time use async code.&lt;/p&gt;
&lt;p&gt;&amp;lt;JSSnippet client:load&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async function doSomething() {
  return Promise.resolve();
}

try {
  await doSomething();
  console.log(&quot;Done!&quot;);
  return 42;
} catch (e) {
  console.error(&quot;Got error:&quot;, e);
} finally {
  console.log(&quot;Finally!&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/JSSnippet&amp;gt;&lt;/p&gt;
&lt;p&gt;Just like in our first example, &lt;code&gt;doSomething&lt;/code&gt; will successfully resolve with
&lt;code&gt;undefined&lt;/code&gt;, after which the rest of the code will execute, printing &lt;code&gt;Done!&lt;/code&gt;
followed by &lt;code&gt;Finally!&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If we reject from &lt;code&gt;doSomething&lt;/code&gt; the result will be similar with the second sync
code example:&lt;/p&gt;
&lt;p&gt;&amp;lt;JSSnippet client:load&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async function doSomething() {
  return Promise.reject(new Error(&quot;Nope&quot;));
}

try {
  await doSomething();
  console.log(&quot;Done!&quot;);
  return 42;
} catch (e) {
  console.error(&quot;Got error:&quot;, e);
} finally {
  console.log(&quot;Finally!&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/JSSnippet&amp;gt;&lt;/p&gt;
&lt;p&gt;In both our examples, the promise has settled, which allowed the execution of
the rest of the code to continue. However, in the recent months, I have stumbled
upon a situation where the promise never settled. We nicknamed that bug the
&lt;code&gt;try/catch/maybe&lt;/code&gt; bug. Here is how it looks like.&lt;/p&gt;
&lt;p&gt;&amp;lt;JSSnippet client:load&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const someCondition = false; 
async function doSomething() {
  return new Promise(resolve =&amp;gt; {
    if (someCondition) {
      resolve(); // never resolves because our condition is `false`
    }
  });
}

try {
  await doSomething();
  console.log(&quot;Done!&quot;);
  return 42;
} catch (e) {
  console.error(&quot;Got error:&quot;, e);
} finally {
  console.log(&quot;Finally!&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/JSSnippet&amp;gt;&lt;/p&gt;
&lt;p&gt;It&apos;s not rocket science, but it took us a little bit of debugging until
we realized that we are not resolving our promises. That&apos;s why I advice to be careful
when using conditionals in the &lt;code&gt;new Promise&lt;/code&gt; constructor handler.&lt;/p&gt;
</content:encoded><category>javascript</category><category>async-await</category><category>promises</category><category>try-catch-finally</category><author>Elvis Adomnica</author></item><item><title>JavaScript hoisting and IE polyfills</title><link>https://erranto.com/blog/javascript-hoisting-and-ie-polyfills</link><guid isPermaLink="true">https://erranto.com/blog/javascript-hoisting-and-ie-polyfills</guid><pubDate>Sat, 05 Oct 2024 15:42:09 GMT</pubDate><content:encoded>&lt;p&gt;import JSSnippet from &quot;../../components/JSSnippet.svelte&quot;&lt;/p&gt;
&lt;p&gt;It has been a very long time since I last had to deal with hoisting in
JavaScript. If my memory serves me correctly, it had something to do with &lt;a href=&quot;https://jestjs.io/docs/manual-mocks#using-with-es-module-imports&quot;&gt;using
ES module imports in
Jest&lt;/a&gt;. Oh
man... I have never enjoyed using Jest. It always felt like I needed to fight it
to get it to do what I want. Or maybe it was just &quot;skill issue&quot;.&lt;/p&gt;
&lt;p&gt;Fast forward to a few weeks ago, and voilà — JavaScript hoisting strikes again.
This time, it had to do with Internet Explorer (IE)
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/Polyfill&quot;&gt;polyfills&lt;/a&gt;. Yep, IE
— that old thing that made our lives as web developers a little bit more
&quot;&lt;em&gt;colorful&lt;/em&gt;&quot;. If you ever had to write code to support it, I bet you remember
having to include a gazillion polyfills just to make it behave like the rest of
the browsers.&lt;/p&gt;
&lt;p&gt;Luckily for us, &lt;a href=&quot;https://learn.microsoft.com/en-us/lifecycle/announcements/internet-explorer-11-end-of-support&quot;&gt;Microsoft ended support for IE 11 on June 25,
2022&lt;/a&gt;,
and has encouraged its users to move to the Chromium-based Microsoft Edge. This
means we no longer needed to include the polyfills, and we could, therefore,
remove a couple of hundred lines of code from our bundle. Easy peasy — except
our Cypress tests running on Chrome started to fail. Whoot?? How does removing
IE polyfills make Chrome tests fail? The short answer...&lt;/p&gt;
&lt;p&gt;&amp;lt;img style=&quot;margin: 0 auto; max-width: 95%;&quot;
src=&quot;https://i.imgflip.com/940vzu.jpg&quot; alt=&quot;Ancient aliens meme depicting
JavaScript&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;For the long answer, we will have to dive into how the scope works in
JavaScript, what is hoisting, and how the polyfill detection phase was
implemented. So let&apos;s get started.&lt;/p&gt;
&lt;h2&gt;JavaScript scope and hoisting&lt;/h2&gt;
&lt;p&gt;Most of the bad reputation that the language gets is coming from the way the
scope was implemented and how hard it is to rationalize about the value of the
&lt;code&gt;this&lt;/code&gt; reference. Up until the introduction of the array functions, it was very
common to see code like &lt;code&gt;var that = this;&lt;/code&gt; or &lt;code&gt;var self = this&lt;/code&gt;. Anyway, it will
take too much space to cover the quirks of the &lt;code&gt;this&lt;/code&gt; keyword here, so I will
only focus on simple variables and functions.&lt;/p&gt;
&lt;p&gt;Traditionally, variable declaration was done using the &lt;code&gt;var&lt;/code&gt; keyword. If a
variable is declared but not initialized, it will by default have an &lt;code&gt;undefined&lt;/code&gt;
value.&lt;/p&gt;
&lt;p&gt;&amp;lt;JSSnippet client:load&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var name = &quot;Moo&quot;;
var uninitialized;

console.log(name);
console.log(uninitialized);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/JSSnippet&amp;gt;&lt;/p&gt;
&lt;p&gt;So far, so good. We have created two variables and printed their values. The
&lt;code&gt;name&lt;/code&gt; variable was given an initial value, while we left the &lt;code&gt;uninitialized&lt;/code&gt;
variable, well... uninitialized. Things start to make less sense when you
encounter code similar to the one below.&lt;/p&gt;
&lt;p&gt;&amp;lt;JSSnippet client:only=&quot;svelte&quot; showLineNumbers={true}&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function fun(cond) {
  if (cond) {
    var someValue = &quot;some-value&quot;;
  }

  console.log(someValue);
}

fun(true);
fun(false);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/JSSnippet&amp;gt;&lt;/p&gt;
&lt;p&gt;In a sane programming language, you&apos;d expect line 6 to throw an error like
&lt;code&gt;ReferenceError: someValue is not defined&lt;/code&gt;. Not in JavaScript, though! Before
the introduction of &lt;code&gt;let&lt;/code&gt; and &lt;code&gt;const&lt;/code&gt;, a variable would belong to one of two
scopes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;function scope&lt;/em&gt;, if the variable was declared inside a function;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;global scope&lt;/em&gt;, if the variable was declared outside of a function.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the scenario above, &lt;code&gt;someValue&lt;/code&gt; is visible throughout the function scope even
though it was declared inside an &lt;code&gt;if&lt;/code&gt; block. This is what we refer to as
&lt;strong&gt;&lt;em&gt;hoisting&lt;/em&gt;&lt;/strong&gt;: a variable declared with &lt;code&gt;var&lt;/code&gt; is visible from the beginning of
its scope regardless of the position in code where it was declared.&lt;/p&gt;
&lt;p&gt;To make things worse, if you by mistake omit the &lt;code&gt;var&lt;/code&gt; keyword (e.g. you
misspell the variable name), you will create a global variable.&lt;/p&gt;
&lt;p&gt;&amp;lt;JSSnippet client:only=&quot;svelte&quot; showLineNumbers={true}&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function fun(cond) {
  var someValue;

  if (cond) {
    somValue = &quot;some-value&quot;; // misspelled
  }

  console.log(someValue);
}

fun(true); // creates global variable &apos;somValue&apos;
fun(false);

console.log(somValue); // misspelled again, prints &apos;some-value&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/JSSnippet&amp;gt;&lt;/p&gt;
&lt;h3&gt;Strict mode&lt;/h3&gt;
&lt;p&gt;One way to prevent such mistakes is to use &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode&quot;&gt;strict
mode&lt;/a&gt;.
In strict mode, line 7 throws &lt;code&gt;ReferenceError: assignment to undeclared variable somValue&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&amp;lt;JSSnippet client:only=&quot;svelte&quot; showLineNumbers={true}&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;use strict&quot;;

function fun(cond) {
  var someValue;

  if (cond) {
    somValue = &quot;some-value&quot;; // misspelled, but throws an error
  }

  console.log(someValue);
}

fun(true);
fun(false);

console.log(somValue);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/JSSnippet&amp;gt;&lt;/p&gt;
&lt;p&gt;While I recommend to always use strict mode, it does not help us with hoisting
issues. Take the following code as an example: &lt;code&gt;someValue&lt;/code&gt; is still hoisted, and
line 4 will simply print &lt;code&gt;undefined&lt;/code&gt; instead of throwing an error.&lt;/p&gt;
&lt;p&gt;&amp;lt;JSSnippet client:only=&quot;svelte&quot; showLineNumbers={true}&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;use strict&quot;;

function fun() {
  console.log(someValue);

  {
    var someValue = &quot;some-value&quot;; // variable is declared inside a block
  }

  console.log(someValue);
}

fun();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/JSSnippet&amp;gt;&lt;/p&gt;
&lt;h3&gt;Let and const&lt;/h3&gt;
&lt;p&gt;To fix the issues that come from using the var keyword, JavaScript introduced
two ways of declaring block-scoped variables: &lt;code&gt;let&lt;/code&gt; and &lt;code&gt;const&lt;/code&gt;. &lt;code&gt;let&lt;/code&gt; is used
when we want to reassign the variable later, while &lt;code&gt;const&lt;/code&gt; is used when we never
need to reassign it. Keep in mind that &lt;code&gt;const&lt;/code&gt; only prevents reassignment; it
&lt;strong&gt;does not&lt;/strong&gt; make the variable immutable.&lt;/p&gt;
&lt;p&gt;&amp;lt;JSSnippet client:only=&quot;svelte&quot;&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const person = {
  name: &quot;Moo&quot;
}

person.name = &quot;Foo&quot;; // mutates the name property of our object

console.log(JSON.stringify(person, undefined, 2));

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/JSSnippet&amp;gt;&lt;/p&gt;
&lt;p&gt;Let&apos;s return to our previous example and see if &lt;code&gt;const&lt;/code&gt; will make our code more
predictable.&lt;/p&gt;
&lt;p&gt;&amp;lt;JSSnippet client:only=&quot;svelte&quot;&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function fun() {
  // throws `ReferenceError: someValue is not defined` because `someValue` is only visible within its block
  console.log(someValue);

  { // `someValue` is only visible inside this block
    const someValue = &quot;some-value&quot;;
  }

  console.log(someValue);
}

fun();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/JSSnippet&amp;gt;&lt;/p&gt;
&lt;p&gt;What about hoisting, is that also fixed? Oh well, line 3 finally throws an error!&lt;/p&gt;
&lt;p&gt;&amp;lt;JSSnippet client:only=&quot;svelte&quot; showLineNumbers={true}&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function fun() {
  // throws `ReferenceError: can&apos;t access lexical declaration &apos;someValue&apos; before initialization`
  console.log(someValue);

  const someValue = &quot;some-value&quot;;

  console.log(someValue);
}

fun();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/JSSnippet&amp;gt;&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;let&lt;/code&gt; and &lt;code&gt;const&lt;/code&gt; we are in a much better place: variables
are only visible in the block where they were declared, and we receive an error
if we try to access a variable before its declaration.&lt;/p&gt;
&lt;h2&gt;The MouseEvent polyfill&lt;/h2&gt;
&lt;p&gt;Now that we have seen some of the quirks of JavaScript, we can return to our
main story. Back when the team had to support IE, they ran into the typical API
differences that required the use of a polyfill. In this case, it was the
&lt;code&gt;MouseEvent&lt;/code&gt; polyfill, as can be seen on this
&lt;a href=&quot;https://stackoverflow.com/questions/28815845/mouseevent-not-working-in-internet-explorer&quot;&gt;StackOverflow&lt;/a&gt;
page. &lt;a href=&quot;https://stackoverflow.com/a/43640602/3415496&quot;&gt;One of the answers&lt;/a&gt; there
suggest that there is already a polyfill available on Mozilla Developer Network
(MDN), &quot;except that the try catch block in that code needs to look like this&quot;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;try {
  new CustomEvent(&apos;test&apos;);
  return false; // No need to polyfill
} catch (e) {
  // Need to polyfill - fall through
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The polyfill has been removed from the main MDN website, but I managed to find
it in &lt;a href=&quot;https://github.com/msn0/mdn-polyfills/blob/master/src/MouseEvent/MouseEvent.js&quot;&gt;a GitHub
repository&lt;/a&gt;,
and it looks &lt;strong&gt;almost&lt;/strong&gt; identical to the one we were using:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export default function () {
    try {
        new MouseEvent(&apos;test&apos;);
        return false; // No need to polyfill
    } catch (e) {
        // Need to polyfill - fall through
    }

    // Polyfills DOM4 MouseEvent
    var MouseEvent = function (eventType, params) {
        params = params || { bubbles: false, cancelable: false };
        var mouseEvent = document.createEvent(&apos;MouseEvent&apos;);
        mouseEvent.initMouseEvent(eventType,
            params.bubbles,
            params.cancelable,
            window,
            0,
            params.screenX || 0,
            params.screenY || 0,
            params.clientX || 0,
            params.clientY || 0,
            params.ctrlKey || false,
            params.altKey || false,
            params.shiftKey || false,
            params.metaKey || false,
            params.button || 0,
            params.relatedTarget || null
        );

        return mouseEvent;
    };

    MouseEvent.prototype = Event.prototype;

    window.MouseEvent = MouseEvent;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At first glance, the code looks correct, although, as one of my colleagues
pointed out, &quot;the &lt;code&gt;try/catch&lt;/code&gt; block is trying to be a little too smart.&quot; And he
was absolutely right. If we learned anything from the previous section, it&apos;s
that a variable declared with var will be hoisted to the beginning of the
function. Let&apos;s add a console log in the &lt;code&gt;catch&lt;/code&gt; block and see what error we get.&lt;/p&gt;
&lt;p&gt;&amp;lt;JSSnippet client:only=&quot;svelte&quot; showLineNumbers={true}&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function fun() {
    try {
        new MouseEvent(&apos;test&apos;);
        return false; // No need to polyfill
    } catch (e) {
        console.log(e);
        // Need to polyfill - fall through
    }

    // Polyfills DOM4 MouseEvent
    var MouseEvent = function (eventType, params) {
        params = params || { bubbles: false, cancelable: false };
        var mouseEvent = document.createEvent(&apos;MouseEvent&apos;);
        mouseEvent.initMouseEvent(eventType,
            params.bubbles,
            params.cancelable,
            window,
            0,
            params.screenX || 0,
            params.screenY || 0,
            params.clientX || 0,
            params.clientY || 0,
            params.ctrlKey || false,
            params.altKey || false,
            params.shiftKey || false,
            params.metaKey || false,
            params.button || 0,
            params.relatedTarget || null
        );

        return mouseEvent;
    };

    MouseEvent.prototype = Event.prototype;

    window.MouseEvent = MouseEvent;
}

fun();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/JSSnippet&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TypeError: MouseEvent is not a constructor&lt;/code&gt; — there we have it! The new
implementation for the &lt;code&gt;MouseEvent&lt;/code&gt; will hoist, and line 3 will call &lt;code&gt;new&lt;/code&gt; on an
&lt;code&gt;undefined&lt;/code&gt; value, instead of a constructor. This meant that the polyfill was
always applied, regardless of the browser used. The version of the polyfill that
I linked earlier uses a different name for the polyfill implementation
(&lt;code&gt;MouseEventPolyfill&lt;/code&gt;), so the name collision due to hoisting won&apos;t happen.&lt;/p&gt;
&lt;p&gt;&amp;lt;JSSnippet client:only=&quot;svelte&quot;&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function fun() {
    try {
        new MouseEvent(&apos;test&apos;);
        console.log(&apos;No need to apply the polyfill&apos;);
        return false; // No need to polyfill
    } catch (e) {
        console.log(e);
        // Need to polyfill - fall through
    }

    // Polyfills DOM4 MouseEvent
    var MouseEventPolyfill = function (eventType, params) {
        params = params || { bubbles: false, cancelable: false };
        var mouseEvent = document.createEvent(&apos;MouseEvent&apos;);
        mouseEvent.initMouseEvent(eventType,
            params.bubbles,
            params.cancelable,
            window,
            0,
            params.screenX || 0,
            params.screenY || 0,
            params.clientX || 0,
            params.clientY || 0,
            params.ctrlKey || false,
            params.altKey || false,
            params.shiftKey || false,
            params.metaKey || false,
            params.button || 0,
            params.relatedTarget || null
        );

        return mouseEvent;
    };

    MouseEventPolyfill.prototype = Event.prototype;

    window.MouseEvent = MouseEventPolyfill;
}

fun();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/JSSnippet&amp;gt;&lt;/p&gt;
&lt;p&gt;Once we realized that our polyfill was always applied and that our tests were
relying on its API, we reworked the tests using the Cypress &lt;code&gt;trigger&lt;/code&gt; API, and we
were happy again.&lt;/p&gt;
&lt;p&gt;It had been ages since I last dealt with a bug caused by hoisting, and this
incident reminded me of the old days of working with JavaScript. I&apos;m glad we&apos;ve
come such a long way since then.&lt;/p&gt;
</content:encoded><category>javascript</category><category>hoisting</category><category>mouse-event</category><category>polyfill</category><author>Elvis Adomnica</author></item><item><title>A TDD implementation of Node.js Path module</title><link>https://erranto.com/blog/tdd-implementation-of-nodejs-path-module</link><guid isPermaLink="true">https://erranto.com/blog/tdd-implementation-of-nodejs-path-module</guid><pubDate>Sat, 20 Jan 2024 21:32:00 GMT</pubDate><content:encoded>&lt;p&gt;import CodeBlock from &quot;../../components/CodeBlock.astro&quot;;&lt;/p&gt;
&lt;p&gt;export function print(x) { return x === 1 ? &quot;+&quot; : &quot;-&quot;; }
export const firstChange = [0, 0, 1, 1, 0,  0, 0];
export const isAbsoluteEmpty = [0, 0, 1, 1, 0, 0, 0, 0, 0];
export const isAbsoluteTests = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0];
export const isAbsoluteImpl = [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0];
export const assertStringTests = [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 0, 0];
export const assertStringImpl = [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0];
export const noSeparatorTests = [0, 0, 0, 0, 2, 1, 1, 1, 0, 2, 1, 0, 2, 1, 0,
2, 1, 0, 0, 0, 0, 0, 0, 0];&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How many TDD practitioners does it take to change a light bulb?&lt;/strong&gt; &lt;em&gt;They don&apos;t
change the bulb; they refactor the darkness!&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I have asked ChatGPT to tell me some jokes about test-driven development (TDD)
and out of 12 generated answers none included unicorns. That&apos;s a bit of a
disappointment considering how similar they are. Everyone is a big advocate of
TDD and how great it is but I am yet to find a team that practices it
consistently.&lt;/p&gt;
&lt;p&gt;Ironically, I&apos;ve chosen to join the advocates group by documenting a
step-by-step &lt;a href=&quot;https://github.com/elvisvoer/b-path&quot;&gt;TDD implementation of the Node.js Path
module&lt;/a&gt;. But before we dive in, you might
be wondering, &lt;em&gt;&lt;strong&gt;&apos;What is test-driven development?&apos;&lt;/strong&gt;&lt;/em&gt; I have asked ChatGPT to
answer this question for you, and here is the result:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Test-Driven Development (TDD) is a software development approach in which tests
are written before the actual code implementation. The TDD process typically
follows these steps:

1. **Write a Test**: Before writing any code, a developer creates a test that
defines a specific function or improvement of the software.

2. **Run the Test**: The newly created test is executed, and it should fail
because the corresponding code to fulfill the requirements hasn&apos;t been
implemented yet.

3. **Write Code**: The developer writes the minimum amount of code necessary to
pass the test. The primary goal is to make the test pass, not to create a
fully-featured solution.

4. **Run Tests**: After writing the code, all tests, including the new one, are
executed to ensure that the recent changes haven&apos;t broken any existing
functionality.

5. **Refactor Code**: If necessary, the code is refactored to improve its
structure, readability, or efficiency. At each step, the tests are rerun to make
sure that the changes haven&apos;t introduced errors.

6. **Repeat**: Steps 1-5 are repeated iteratively for each new piece of
functionality.

TDD is designed to promote a more robust and maintainable codebase by ensuring
that every piece of code has corresponding tests and that new features or
changes do not negatively impact existing functionality.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pretty straight forward, right? TDD not only fosters a more robust and
maintainable codebase but also mitigates the impact of &quot;implementation bias&quot;
(status quo bias). This cognitive bias occurs when a programmer is influenced by
existing code while crafting a new unit of code.&lt;/p&gt;
&lt;p&gt;Consider this scenario: I&apos;ve created a function tasked with parsing and
processing an array of strings (we will actually write that later on in the
&lt;code&gt;path.resolve&lt;/code&gt; implementation). If I initially write the code without accounting
for certain corner cases, chances are high that I&apos;ll overlook those same
scenarios when crafting the corresponding tests. Also, another programmer aiming
to enhance code coverage may be willing to write unit tests for my
implementation. Their focus could be on ensuring each statement is executed at
least once, potentially achieving &lt;a href=&quot;/blog/what-does-code-coverage-measure&quot;&gt;full unit
coverage&lt;/a&gt; without a comprehensive
understanding of the implementation details. Consequently, they might overlook
the same corner cases that eluded me as the original author.&lt;/p&gt;
&lt;p&gt;Additionally, having a test-first approach will ensure less coupling between
your components, and a better design of public interfaces.&lt;/p&gt;
&lt;h2&gt;Project setup&lt;/h2&gt;
&lt;p&gt;The project is configured as a basic JavaScript module without any build steps.
The  &lt;a href=&quot;https://github.com/elvisvoer/b-path/commit/ea3654dc4da7d8e03e2947d3834f023a086ddd9e&quot;&gt;initial
commit&lt;/a&gt;
introduces the necessary GitHub workflow to execute the pipeline, establishes a
straightforward test setup utilizing the &lt;a href=&quot;https://nodejs.org/api/test.html&quot;&gt;built-in Node.js test
runner&lt;/a&gt;, and uses a simple forwarding
mechanism for the &lt;code&gt;node:path&lt;/code&gt; module (as a starting point):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// lib/b-path.js
import path from &quot;node:path&quot;;

export default path;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The TDD approach&lt;/h2&gt;
&lt;p&gt;We are now ready to add our first tests and &lt;a href=&quot;https://github.com/elvisvoer/b-path/actions/runs/7585405770/job/20661199864&quot;&gt;make sure they
fail&lt;/a&gt;
by swapping our forwarding implementation with an empty one. Here is how
that looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// tests/b-path.test.js
import { describe, it } from &quot;node:test&quot;;
import assert from &quot;node:assert/strict&quot;;
import bPath from &quot;../lib/b-path.js&quot;;

describe(&quot;b-path&quot;, () =&amp;gt; {
  describe(&quot;POSIX&quot;, () =&amp;gt; {
    it(&quot;path.delimiter should provide the path delimiter&quot;, () =&amp;gt; {
      assert.strictEqual(bPath.delimiter, &quot;:&quot;);
    });

    it(&quot;path.sep should provide the path separator&quot;, () =&amp;gt; {
      assert.strictEqual(bPath.sep, &quot;/&quot;);
    });
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// lib/b-path.js
const posix = {
  posix: null,
  win32: null,
};

posix.posix = posix;

export default posix;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that we will only focus on the POSIX implementation and completely ignore
the Win32 API. With that in mind, we can now move to &lt;em&gt;&lt;strong&gt;Step 3&lt;/strong&gt;&lt;/em&gt; and write the
actual code, &lt;a href=&quot;https://github.com/elvisvoer/b-path/actions/runs/7585416343/job/20661237405&quot;&gt;making the tests
pass&lt;/a&gt;.
This is as simple as adding the two missing properties to our &lt;code&gt;posix&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;&amp;lt;CodeBlock info={firstChange} print={print}&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// lib/b-path.js
const posix = {
  delimiter: &quot;:&quot;,
  sep: &quot;/&quot;,
  posix: null,
  win32: null,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/CodeBlock&amp;gt;&lt;/p&gt;
&lt;h3&gt;path.isAbsolute(path)&lt;/h3&gt;
&lt;p&gt;Moving on to &lt;code&gt;path.isAbsolute&lt;/code&gt;, we&apos;ll once again start with an empty
implementation and write some tests based on &lt;a href=&quot;https://nodejs.org/api/path.html#pathisabsolutepath&quot;&gt;the official
documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;CodeBlock info={isAbsoluteEmpty} print={print}&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// lib/b-path.js
const posix = {
  isAbsolute: () =&amp;gt; {},

  delimiter: &quot;:&quot;,
  sep: &quot;/&quot;,
  posix: null,
  win32: null,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/CodeBlock&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;br /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;CodeBlock info={isAbsoluteTests} print={print}&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// tests/b-path.test.js
import { describe, it } from &quot;node:test&quot;;
import assert from &quot;node:assert/strict&quot;;
import bPath from &quot;../lib/b-path.js&quot;;
describe(&quot;b-path&quot;, () =&amp;gt; {
  describe(&quot;POSIX&quot;, () =&amp;gt; {
    it(&quot;path.delimiter should provide the path delimiter&quot;, () =&amp;gt; {
      assert.strictEqual(bPath.delimiter, &quot;:&quot;);
    });
    it(&quot;path.sep should provide the path separator&quot;, () =&amp;gt; {
      assert.strictEqual(bPath.sep, &quot;/&quot;);
    });

    // input | expected result
    [
      [&quot;&quot;, false],
      [&quot;.&quot;, false],
      [&quot;foo/&quot;, false],
      [&quot;foo.bar&quot;, false],
      [&quot;/foo/bar&quot;, true],
      [&quot;/baz/..&quot;, true],
    ].forEach(([input, result]) =&amp;gt; {
      it(`path.isAbsolute(&quot;${input}&quot;) should return &apos;${result}&apos;`, () =&amp;gt; {
        assert.strictEqual(bPath.isAbsolute(input), result);
      });
    });
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/CodeBlock&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Step 3&lt;/strong&gt;&lt;/em&gt; rightfully emphasizes that &quot;&lt;em&gt;the primary objective is to ensure the
test passes, rather than creating a fully-featured solution&lt;/em&gt;&quot;. As you can see, I
haven&apos;t written any tests to address scenarios where the input is not a &lt;code&gt;string&lt;/code&gt;
and we must raise a &lt;code&gt;TypeError&lt;/code&gt;. We can incorporate those tests a bit later, but
for now, let&apos;s focus on making our existing tests pass.&lt;/p&gt;
&lt;p&gt;&amp;lt;CodeBlock info={isAbsoluteImpl} print={print}&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// lib/b-path.js
const posix = {
  /**
   * The isAbsolute() method determines if input path is an absolute path.
   * @param {string} path - input path
   * @returns {boolean}
   */
  isAbsolute: (path) =&amp;gt; {
    return !!path &amp;amp;&amp;amp; path.charCodeAt(0) === posix.sep.charCodeAt(0);
  },

  delimiter: &quot;:&quot;,
  sep: &quot;/&quot;,
  posix: null,
  win32: null,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/CodeBlock&amp;gt;&lt;/p&gt;
&lt;p&gt;Running all the tests (&lt;em&gt;&lt;strong&gt;Step 4&lt;/strong&gt;&lt;/em&gt;) will now output the following results:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; b-path@0.0.0 test
&amp;gt; node --test tests

▶ b-path
  ▶ POSIX
    ✔ path.delimiter should provide the path delimiter (0.745045ms)
    ✔ path.sep should provide the path separator (0.164463ms)
    ✔ path.isAbsolute(&quot;&quot;) should return &apos;false&apos; (0.186978ms)
    ✔ path.isAbsolute(&quot;.&quot;) should return &apos;false&apos; (0.125469ms)
    ✔ path.isAbsolute(&quot;foo/&quot;) should return &apos;false&apos; (0.250108ms)
    ✔ path.isAbsolute(&quot;foo.bar&quot;) should return &apos;false&apos; (0.204638ms)
    ✔ path.isAbsolute(&quot;/foo/bar&quot;) should return &apos;true&apos; (1.390664ms)
    ✔ path.isAbsolute(&quot;/baz/..&quot;) should return &apos;true&apos; (0.16196ms)
  ▶ POSIX (4.772938ms)

▶ b-path (6.41931ms)

ℹ tests 8
ℹ suites 2
ℹ pass 8
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 104.492059

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;The not-so-happy path&lt;/h3&gt;
&lt;p&gt;As mentioned earlier, our current code and tests only cover the &quot;happy path&quot;
where the input is provided as expected. Now, let&apos;s address the &quot;not-so-happy
path&quot; and consider various input types. As always, we&apos;ll start with the tests
first.&lt;/p&gt;
&lt;p&gt;&amp;lt;CodeBlock info={assertStringTests} print={print}&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// tests/b-path.test.js
import { describe, it } from &quot;node:test&quot;;
import assert from &quot;node:assert/strict&quot;;
import bPath from &quot;../lib/b-path.js&quot;;
describe(&quot;b-path&quot;, () =&amp;gt; {
  describe(&quot;POSIX&quot;, () =&amp;gt; {
    // ...

    [undefined, null, NaN, 42, BigInt(42), {}, [], Symbol(), false].forEach(
      (input) =&amp;gt; {
        it(`path.isAbsolute should throw TypeError if input type is ${typeof input}`, () =&amp;gt; {
          assert.throws(
            () =&amp;gt; {
              bPath.isAbsolute(input);
            },
            {
              name: &quot;TypeError&quot;,
              message: `The &quot;path&quot; argument must be of type string. Received ${typeof input}`,
            }
          );
        });
      }
    );
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/CodeBlock&amp;gt;&lt;/p&gt;
&lt;p&gt;And then proceed with the actual implementation.&lt;/p&gt;
&lt;p&gt;&amp;lt;CodeBlock info={assertStringImpl} print={print}&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// lib/b-path.js
/**
 * The assertIsString() function verifies whether the input is a string,
 * throwing a TypeError otherwise.
 * @param {*} input
 * @throws {TypeError}
 */
function assertIsString(input) {
  if (typeof input !== &quot;string&quot;) {
    throw new TypeError(
      `The &quot;path&quot; argument must be of type string. Received ${typeof input}`
    );
  }
}

const posix = {
  /**
   * The isAbsolute() method determines if input path is an absolute path.
   * @param {string} path - input path
   * @returns {boolean}
   */
  isAbsolute: (path) =&amp;gt; {
    assertIsString(path);
    return !!path &amp;amp;&amp;amp; path.charCodeAt(0) === posix.sep.charCodeAt(0);
  },

  // ...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/CodeBlock&amp;gt;&lt;/p&gt;
&lt;h3&gt;path.format(pathObject)&lt;/h3&gt;
&lt;p&gt;We continue our reverse engineering process with &lt;code&gt;path.format&lt;/code&gt;. This method
takes a &lt;code&gt;pathObject&lt;/code&gt; as input and returns a &lt;code&gt;path&lt;/code&gt; string based on the rules
specified in &lt;a href=&quot;https://nodejs.org/api/path.html#pathformatpathobject&quot;&gt;the docs&lt;/a&gt;.
We&apos;ll start with the &quot;happy path&quot; where the input is an &lt;code&gt;object&lt;/code&gt; and we&apos;ll deal
with the rest later. Once we have completed our implementation, we can swap it
back with the &apos;forwarding mechanism&apos; from the project setup and check that we
have the right functionality.&lt;/p&gt;
&lt;p&gt;Documenting every single step here would occupy too much space, instead, I have
tagged the commits with pre-release tags to get a better overview. The &lt;a href=&quot;https://github.com/elvisvoer/b-path/compare/v0.1.0-rc0...v0.1.0-rc1&quot;&gt;very
first
version&lt;/a&gt; is
somehow naive but it does cover most of the cases, which is a great starting
point.&lt;/p&gt;
&lt;p&gt;We should now handle the following scenario: &quot;&lt;em&gt;If only &lt;code&gt;root&lt;/code&gt; is provided or
&lt;code&gt;dir&lt;/code&gt; is equal to &lt;code&gt;root&lt;/code&gt; then the platform separator will not be included&lt;/em&gt;&quot;. To
do so, we need to &lt;a href=&quot;https://github.com/elvisvoer/b-path/commit/c3aae309dc9c476de37013487b1bb0fcb392c84b?diff=unified&amp;amp;w=0&quot;&gt;make some changes to our
tests&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;CodeBlock info={noSeparatorTests} print={print}&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// input | expected result
[
  [{}, &quot;&quot;],
  [{ root: &quot;/root&quot; }, &quot;/root&quot;],
  [{ root: &quot;/root&quot;, name: &quot;file&quot;, ext: &quot;.txt&quot; }, &quot;/root/file.txt&quot;],
  // only root is present so no separator is included
  [{ root: &quot;/root&quot;, name: &quot;file&quot;, ext: &quot;.txt&quot; }, &quot;/rootfile.txt&quot;],
  [{ root: &quot;/root/&quot;, name: &quot;file&quot;, ext: &quot;.txt&quot; }, &quot;/root/file.txt&quot;],
  // should add the extension &quot;.&quot; if missing
  [{ root: &quot;/root&quot;, name: &quot;file&quot;, ext: &quot;txt&quot; }, &quot;/root/file.txt&quot;],
  [{ root: &quot;/root/&quot;, name: &quot;file&quot;, ext: &quot;txt&quot; }, &quot;/root/file.txt&quot;],
  // should ignore &apos;name&apos; and &apos;ext&apos; if &apos;base&apos; is present
  [{ root: &quot;/root&quot;, base: &quot;base.sh&quot;, name: &quot;file&quot;, ext: &quot;.txt&quot; }, &quot;/root/base.sh&quot;],
  [{ root: &quot;/root/&quot;, base: &quot;base.sh&quot;, name: &quot;file&quot;, ext: &quot;.txt&quot; }, &quot;/root/base.sh&quot;],
  // should ignore &apos;root&apos; if &apos;dir&apos; is present
  [{ root: &quot;/root&quot;, dir: &quot;/dir&quot;, base: &quot;file.txt&quot;}, &quot;/dir/file.txt&quot;],
  [{ root: &quot;/root/&quot;, dir: &quot;/dir&quot;, base: &quot;file.txt&quot;}, &quot;/dir/file.txt&quot;],
].forEach(([input, result]) =&amp;gt; {
  it(`path.format(${JSON.stringify(
    input
  )}) should return &apos;${result}&apos;`, () =&amp;gt; {
    assert.strictEqual(bPath.format(input), result);
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/CodeBlock&amp;gt;&lt;/p&gt;
&lt;p&gt;We may attempt to correct our implementation by adjusting the return statement
to &lt;code&gt;return result.join(&quot;&quot;);&lt;/code&gt;. However, this adjustment would lead to a failure in
our &lt;code&gt;dir&lt;/code&gt; test:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;✖ failing tests:

✖ path.format({&quot;root&quot;:&quot;/root/&quot;,&quot;dir&quot;:&quot;/dir&quot;,&quot;base&quot;:&quot;file.txt&quot;}) should return &apos;/dir/file.txt&apos; (2.433298ms)
  AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
  + actual - expected

  + &apos;/dirfile.txt&apos;
  - &apos;/dir/file.txt&apos;
         ^
      at TestContext.&amp;lt;anonymous&amp;gt; (file:///home/elvis/eadomnica/b-path/tests/b-path.test.js:62:16)
      at Test.runInAsyncScope (node:async_hooks:206:9)
      at Test.run (node:internal/test_runner/test:580:25)
      at Suite.processPendingSubtests (node:internal/test_runner/test:325:18)
      at Test.postRun (node:internal/test_runner/test:649:19)
      at Test.run (node:internal/test_runner/test:608:10)
      at async Suite.processPendingSubtests (node:internal/test_runner/test:325:7) {
    generatedMessage: true,
    code: &apos;ERR_ASSERTION&apos;,
    actual: &apos;/dirfile.txt&apos;,
    expected: &apos;/dir/file.txt&apos;,
    operator: &apos;strictEqual&apos;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can address this issue by ensuring that &lt;code&gt;dir&lt;/code&gt; always includes a trailing
slash, much like how we handled the absence of a dot in the extension. Here&apos;s
what our &lt;a href=&quot;https://github.com/elvisvoer/b-path/compare/v0.1.0-rc1...v0.1.0-rc2&quot;&gt;updated
solution&lt;/a&gt;
would look like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * The path.format() method returns a path string from an object.
 * This is the opposite of `path.parse()`.
 * @param {object} pathObject
 * @param {string} pathObject.root
 * @param {string} pathObject.dir
 * @param {string} pathObject.base
 * @param {string} pathObject.name
 * @param {string} pathObject.ext
 * @returns {string}
 */
format: (pathObject) =&amp;gt; {
  const result = [];

  const root = pathObject.root || &quot;&quot;;
  let dir = pathObject.dir || &quot;&quot;;
  if (dir &amp;amp;&amp;amp; dir.charCodeAt(dir.length - 1) !== posix.sep.charCodeAt(0)) {
    dir = `${dir}${posix.sep}`;
  }
  const base = pathObject.base || &quot;&quot;;
  const name = pathObject.name || &quot;&quot;;
  let ext = pathObject.ext || &quot;&quot;;
  if (ext &amp;amp;&amp;amp; ext.charCodeAt(0) !== &quot;.&quot;.charCodeAt(0)) {
    ext = `.${ext}`;
  }

  const dirName = dir || root;
  const baseName = base || `${name}${ext}`;

  if (dirName) result.push(dirName);
  if (baseName) result.push(baseName);

  return result.join(&quot;&quot;);
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&apos;ll now proceed to substitute our current implementation with the &apos;forwarding
mechanism&apos; to confirm its correctness. Please note that the &lt;code&gt;assertIsString&lt;/code&gt;
function doesn&apos;t yield the same &lt;code&gt;TypeError&lt;/code&gt; message, so we&apos;ll need to comment out
those specific tests. Additionally, I&apos;ll take this opportunity to write more
tests and cover some other corner cases.&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;This is slightly unexpected, only &lt;a href=&quot;https://github.com/elvisvoer/b-path/actions/runs/7600686450/job/20699202829&quot;&gt;Node.js v20.x tests are
passing&lt;/a&gt;.
Nonetheless, I find it okay since &lt;a href=&quot;https://nodejs.org/en/blog/release/v20.9.0&quot;&gt;v20.x transitioned to a Long Term Support
version&lt;/a&gt; last October.&lt;/p&gt;
&lt;p&gt;As for the extra tests, I went ahead and included &lt;a href=&quot;https://github.com/elvisvoer/b-path/commit/8716862de2f3dca0fb005e8280afc9b3fd758422&quot;&gt;a few more test
cases&lt;/a&gt;.
What caught me off guard was that a trailing slash is added to the &lt;code&gt;dir&lt;/code&gt;
property even if it already exists. The only scenario where this doesn&apos;t occur
is if &lt;code&gt;root&lt;/code&gt; is equal to &lt;code&gt;dir&lt;/code&gt;, as specified in the docs. I had anticipated that
the resulting path would be normalized but it doesn&apos;t seem so. Consequently, I
had to modify my implementation to match the original behavior by making the
following change:&lt;/p&gt;
&lt;p&gt;&amp;lt;CodeBlock info={[0, 0, 2, 1, 0, 0, 0]} print={print}&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const root = pathObject.root || &quot;&quot;;
let dir = pathObject.dir || &quot;&quot;;
if (dir &amp;amp;&amp;amp; dir.charCodeAt(dir.length - 1) !== posix.sep.charCodeAt(0))
if (dir &amp;amp;&amp;amp; dir !== root) {
  dir = `${dir}${posix.sep}`;
}
const base = pathObject.base || &quot;&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/CodeBlock&amp;gt;&lt;/p&gt;
&lt;p&gt;The full diff of the TDD approach for &lt;code&gt;path.format&lt;/code&gt; (including the input check)
can be seen
&lt;a href=&quot;https://github.com/elvisvoer/b-path/compare/v0.1.0-rc0...v0.1.0-rc3&quot;&gt;here&lt;/a&gt;.
Keep in mind that the commit at the bottom of the page is in fact our top commit.&lt;/p&gt;
&lt;h3&gt;path.normalize&lt;/h3&gt;
&lt;p&gt;Before we continue with &lt;code&gt;path.parse&lt;/code&gt; and friends (&lt;code&gt;path.basename&lt;/code&gt;,
&lt;code&gt;path.dirname&lt;/code&gt;, &lt;code&gt;path.extname&lt;/code&gt;), let&apos;s address one of the elephants in the room:
&lt;code&gt;path.normalize&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We can start by &lt;a href=&quot;https://github.com/elvisvoer/b-path/compare/v0.1.0-rc3...v0.1.0-rc4&quot;&gt;modifying the input sanity check
test&lt;/a&gt; from
&lt;code&gt;path.isAbsolute&lt;/code&gt; to include our new method. With that out of the way, we
can now think of the best approach in handling such a complex function.&lt;/p&gt;
&lt;p&gt;I must admit that I have already done this exercise last
year, but I have deleted my repository, so I will have to do this all over
again. If I remember correctly, the approach I took was to split the string by
the &lt;code&gt;path.sep&lt;/code&gt; and use a &lt;code&gt;reduce&lt;/code&gt; function to compute the normalized path. While
this will work, I cannot stop and think about the amount of intermediate objects
created and the time spent traversing the input multiple times. Can we do the
normalization in one go? Well, I guess we can, with the good-old &lt;code&gt;for&lt;/code&gt; loop.&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;Alright! &lt;a href=&quot;https://github.com/elvisvoer/b-path/compare/v0.1.0-rc4...v0.1.0-rc5&quot;&gt;The implementation so
far&lt;/a&gt; is not
as crazy as I would have thought, but there are definitely some unhandled corner
cases that I will be adding later. At this point, there are a total of 20
commits, &lt;a href=&quot;https://github.com/elvisvoer/b-path/commit/6148439491b08c48d430136ccc719f2a591e8873&quot;&gt;one of
which&lt;/a&gt;
includes a small refactoring. This is by far the biggest advantage of
test-driven development: you can refactor your code as you see fit while having
the reassurance that it will still function correctly. And, on top of that, we
preserved 100% code coverage.&lt;/p&gt;
&lt;p&gt;After adding the &lt;a href=&quot;https://github.com/elvisvoer/b-path/commit/e2eda3d05526cc176a545bab940100b67321fb87&quot;&gt;promised corner
cases&lt;/a&gt;,
there was a need for some &lt;a href=&quot;https://github.com/elvisvoer/b-path/commit/4b7f40327647d0126a07ba7225d317240b353078&quot;&gt;small
adjustments&lt;/a&gt;,
mainly around how to handle navigating up from the top directory. But wait, what
about some paths that make absolutely no sense? Something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[&quot;...&quot;, &quot;...&quot;], // makes no sense
[&quot;.../.&quot;, &quot;...&quot;], // same
[&quot;.../..&quot;, &quot;.&quot;], // same
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We are reverse engineering the existing behavior so I guess I will have to go
ahead and &lt;a href=&quot;https://github.com/elvisvoer/b-path/commit/494bef2601d28c9bf8d9335c3eeca7c330262514&quot;&gt;fix those as
well&lt;/a&gt;.
Even with such a non-sense corner case, our final implementation doesn&apos;t look
very complex. Of course, as the author of the code I am extremely biased on
this. And actually, as I pasted the code here, I realized &lt;a href=&quot;https://github.com/elvisvoer/b-path/commit/e1e3d8e501066c4e26cd001c71157a5380932036&quot;&gt;I can do one more
improvement&lt;/a&gt;.
This is the final final version, I promise (unless you find a bug).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * Normalize a string path, reducing &apos;..&apos; and &apos;.&apos; parts.
 * When multiple slashes are found, they&apos;re replaced by a single one;
 * when the path contains a trailing slash, it is preserved.
 *
 * @param path string path to normalize.
 * @throws {TypeError} if `path` is not a string.
 */
normalize: (path) =&amp;gt; {
  assertIsString(path);

  if (path.length === 0) {
    return &quot;.&quot;;
  }

  if (path.length === 1) {
    return path;
  }

  const hasTrailingSep = path.charAt(path.length - 1) === posix.sep;
  const absolute = posix.isAbsolute(path);
  const fragments = [];
  let word = &quot;&quot;;
  let dots = 0;

  const handleFragment = () =&amp;gt; {
    if (dots &amp;gt; 2) {
      fragments.push(Array(dots).fill(&quot;.&quot;).join(&quot;&quot;));
    }

    if (dots === 2) {
      if (fragments.length &amp;amp;&amp;amp; fragments[fragments.length - 1] !== &quot;..&quot;) {
        // navigate up but don&apos;t pop an existing &quot;..&quot;
        fragments.pop();
      } else if (!absolute) {
        // top reached and relative path,
        // absolute path is handled in the if (fragments.length === 0) below
        fragments.push(&quot;..&quot;);
      }
    }

    if (word.length) {
      fragments.push(word);
    }
  };

  for (let i = 0; i &amp;lt; path.length; i += 1) {
    // duplicated sep, skip
    if (
      path.charAt(i) === posix.sep &amp;amp;&amp;amp;
      i - 1 &amp;gt;= 0 &amp;amp;&amp;amp;
      path.charAt(i - 1) === posix.sep
    ) {
      continue;
    }

    if (path.charAt(i) === &quot;.&quot;) {
      dots += 1;
      continue;
    }

    if (path.charAt(i) === posix.sep) {
      handleFragment();

      word = &quot;&quot;;
      dots = 0;
      continue;
    }

    word += path.charAt(i);
  }

  handleFragment();

  if (fragments.length === 0) {
    if (absolute) {
      return posix.sep;
    }

    return hasTrailingSep ? &quot;./&quot; : &quot;.&quot;;
  }

  let result = fragments.join(posix.sep);
  if (hasTrailingSep) {
    result += posix.sep;
  }

  return result;
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For comparison, I have found a package that contains the posix-only
implementation called
&lt;a href=&quot;https://github.com/browserify/path-browserify/blob/master/index.js&quot;&gt;path-browserify&lt;/a&gt;,
which is an extract from Node.js v8.11.1 codebase. This is how that looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Resolves . and .. elements in a path with directory names
function normalizeStringPosix(path, allowAboveRoot) {
  var res = &apos;&apos;;
  var lastSegmentLength = 0;
  var lastSlash = -1;
  var dots = 0;
  var code;
  for (var i = 0; i &amp;lt;= path.length; ++i) {
    if (i &amp;lt; path.length)
      code = path.charCodeAt(i);
    else if (code === 47 /*/*/)
      break;
    else
      code = 47 /*/*/;
    if (code === 47 /*/*/) {
      if (lastSlash === i - 1 || dots === 1) {
        // NOOP
      } else if (lastSlash !== i - 1 &amp;amp;&amp;amp; dots === 2) {
        if (res.length &amp;lt; 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== 46 /*.*/ || res.charCodeAt(res.length - 2) !== 46 /*.*/) {
          if (res.length &amp;gt; 2) {
            var lastSlashIndex = res.lastIndexOf(&apos;/&apos;);
            if (lastSlashIndex !== res.length - 1) {
              if (lastSlashIndex === -1) {
                res = &apos;&apos;;
                lastSegmentLength = 0;
              } else {
                res = res.slice(0, lastSlashIndex);
                lastSegmentLength = res.length - 1 - res.lastIndexOf(&apos;/&apos;);
              }
              lastSlash = i;
              dots = 0;
              continue;
            }
          } else if (res.length === 2 || res.length === 1) {
            res = &apos;&apos;;
            lastSegmentLength = 0;
            lastSlash = i;
            dots = 0;
            continue;
          }
        }
        if (allowAboveRoot) {
          if (res.length &amp;gt; 0)
            res += &apos;/..&apos;;
          else
            res = &apos;..&apos;;
          lastSegmentLength = 2;
        }
      } else {
        if (res.length &amp;gt; 0)
          res += &apos;/&apos; + path.slice(lastSlash + 1, i);
        else
          res = path.slice(lastSlash + 1, i);
        lastSegmentLength = i - lastSlash - 1;
      }
      lastSlash = i;
      dots = 0;
    } else if (code === 46 /*.*/ &amp;amp;&amp;amp; dots !== -1) {
      ++dots;
    } else {
      dots = -1;
    }
  }
  return res;
}

//...

function normalize(path) {
  assertPath(path);

  if (path.length === 0) return &apos;.&apos;;

  var isAbsolute = path.charCodeAt(0) === 47 /*/*/;
  var trailingSeparator = path.charCodeAt(path.length - 1) === 47 /*/*/;

  // Normalize the path
  path = normalizeStringPosix(path, !isAbsolute);

  if (path.length === 0 &amp;amp;&amp;amp; !isAbsolute) path = &apos;.&apos;;
  if (path.length &amp;gt; 0 &amp;amp;&amp;amp; trailingSeparator) path += &apos;/&apos;;

  if (isAbsolute) return &apos;/&apos; + path;
  return path;
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can be the judge of which version is easier to grasp.&lt;/p&gt;
&lt;h3&gt;path.parse(path)&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;To be continued...&lt;/em&gt;&lt;/p&gt;
</content:encoded><category>test-driven-development</category><category>path</category><category>nodejs</category><author>Elvis Adomnica</author></item><item><title>What does code coverage measure?</title><link>https://erranto.com/blog/what-does-code-coverage-measure</link><guid isPermaLink="true">https://erranto.com/blog/what-does-code-coverage-measure</guid><pubDate>Fri, 03 Nov 2023 18:44:22 GMT</pubDate><content:encoded>&lt;p&gt;import CodeBlock from &quot;../../components/CodeBlock.astro&quot;;
export const coverage = [0, 0, 5, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1]&lt;/p&gt;
&lt;p&gt;This week, I was having a conversation with another engineer, and they had an
interesting remark. They claimed that devops should have some control over a
project pipeline workflow in case we impose a code coverage threshold.
Otherwise, &quot;an engineer would be able to just lower it to pass the pipeline&quot;.
There are a few issues with this claim:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The change to lower the coverage threshold would show in the review, so it is
not very easy to sneak it in.&lt;/li&gt;
&lt;li&gt;An engineer can do much more damage than cheating the code coverage threshold
in an environment governed by mistrust.&lt;/li&gt;
&lt;li&gt;If the entire dev team is &quot;evil&quot;, even if they don&apos;t have control over the
config for the threshold, they can still easily cheat the code coverage instead.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;What does code coverage measure?&lt;/h3&gt;
&lt;p&gt;Code coverage measures &lt;strong&gt;lines of code executed&lt;/strong&gt;, &lt;strong&gt;not lines of code
validated&lt;/strong&gt;, as we might mistakenly expect. To cheat, one can write some unit
tests that are providing just the right input (in order to cover as many lines of
code as possible) without any consideration for the results of the code
execution. Take the following silly function as an example.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// foo.js
function foo(...args) {
  switch (args.length) {
    case 0:
      throw new Error(&quot;Expected at least 1 param&quot;);
    case 1:
      throw new Error(&quot;Neh, 2 would do&quot;);
    case 2:
      return 2;
    case 3:
      return undef.something;

    default:
      return -1;
  }
}

module.exports = foo;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can easily cover each branch of the &lt;code&gt;switch&lt;/code&gt; statement by calling &lt;code&gt;foo&lt;/code&gt; with
&lt;code&gt;0&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;2&lt;/code&gt;, &lt;code&gt;3&lt;/code&gt;, and &lt;code&gt;4&lt;/code&gt; arguments. We can do this in a simple &lt;code&gt;for&lt;/code&gt; loop.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for (let i = 0; i &amp;lt; 5; i++) {
  const args = Array.from(Array(i).keys()); // [0..i]
  foo(...args);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is how the entire test file looks like. We guarded our function calls in a
&lt;code&gt;try/catch&lt;/code&gt;, and we are only doing a dummy assertion at the end.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// foo.test.js
const foo = require(&quot;./foo&quot;);

test(&quot;test foo&quot;, () =&amp;gt; {
  // call test function ignoring its execution
  for (let i = 0; i &amp;lt; 5; i++) {
    try {
      const args = Array.from(Array(i).keys()); // [0..i]
      foo(...args);
    } catch {}
  }

  expect(true).toBeTruthy();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To make everyone&apos;s life easier, I put all the code together on my &lt;a href=&quot;https://github.com/elvisvoer/code-cove-example&quot;&gt;GitHub
page&lt;/a&gt;. After installing the
dependencies and running &lt;code&gt;npm run test -- --coverage&lt;/code&gt;, we will get a report
similar to the one below. We have indeed covered all the branches of the
&lt;code&gt;switch&lt;/code&gt; statement, while all we asserted is if &lt;code&gt;true&lt;/code&gt; is a value that is
coerced to &lt;code&gt;true&lt;/code&gt; when a &lt;code&gt;boolean&lt;/code&gt; is expected.&lt;/p&gt;
&lt;p&gt;&amp;lt;CodeBlock info={coverage} showLineNumbers={true} print={(d) =&amp;gt; &lt;code&gt;${d}x&lt;/code&gt;}&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// foo.js
function foo(...args) {
  switch (args.length) {
    case 0:
      throw new Error(&quot;Expected at least 1 param&quot;);
    case 1:
      throw new Error(&quot;Neh, 2 would do&quot;);
    case 2:
      return 2;
    case 3:
      return undef.something;

    default:
      return -1;
  }
}

module.exports = foo;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/CodeBlock&amp;gt;&lt;/p&gt;
&lt;p&gt;Nice cheat! We didn&apos;t even care that &lt;code&gt;undef.something&lt;/code&gt; will throw an
&lt;code&gt;Uncaught ReferenceError: undef is not defined&lt;/code&gt; exception, all the errors
were caught and the test passed.&lt;/p&gt;
&lt;h3&gt;How does code coverage work?&lt;/h3&gt;
&lt;p&gt;When we pass &lt;code&gt;--coverage&lt;/code&gt; to the test runner (&lt;a href=&quot;https://jestjs.io/&quot;&gt;jest&lt;/a&gt; in my
case), our code is first preprocessed to include some extra code which will do
the coverage line counting. Here is how a very simplified version of that would
look like.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const cov = Array(18).fill(0);// foo.js
function foo(...args) {
cov[2]++;switch (args.length) {
    case 0:
cov[4]++;throw new Error(&quot;Expected at least 1 param&quot;);
    case 1:
cov[6]++;throw new Error(&quot;Neh, 2 would do&quot;);
    case 2:
cov[8]++;return 2;
    case 3:
cov[10]++;return undef.something;

    default:
cov[13]++;return -1;
  }
}

cov[17]++;module.exports = foo;
module.exports.__cov__ = cov;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can now simulate our test coverage with the same loop we used earlier.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const foo = require(&quot;./foo-cov&quot;);

for (let i = 0; i &amp;lt; 5; i++) {
  try {
    const args = Array.from(Array(i).keys()); // [0..i]
    foo(...args);
  } catch {}
}

console.log(JSON.stringify(foo.__cov__));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Et voilà, it printed the same array that I&apos;ve used to configure the &lt;a href=&quot;https://github.com/elvisvoer/erranto-site/blob/main/src/components/CodeBlock.astro&quot;&gt;code
coverage
component&lt;/a&gt;
used earlier in the article.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import CodeBlock from &quot;../../components/CodeBlock.astro&quot;;
export const coverage = [0, 0, 5, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1]

&amp;lt;CodeBlock info={coverage} showLineNumbers={true} print={(d) =&amp;gt; `${d}x`}&amp;gt;
```javascript
// foo.js
function foo(...args) {
  switch (args.length) {
    case 0:
      throw new Error(&quot;Expected at least 1 param&quot;);
    case 1:
      throw new Error(&quot;Neh, 2 would do&quot;);
    case 2:
      return 2;
    case 3:
      return undef.something;

    default:
      return -1;
  }
}

module.exports = foo;
```
&amp;lt;/CodeBlock&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Is code coverage useless then?&lt;/h3&gt;
&lt;p&gt;No, it is not. Code coverage is a great tool to help you understand how well you
are doing on testing. But, like with any type of measurements, you should take
its results with a grain of salt. For instance, &lt;a href=&quot;https://jestjs.io/docs/snapshot-testing&quot;&gt;snapshot
testing&lt;/a&gt; could lead to false positives
in terms of line of code covered but not properly verified. Also, imposing a
ridiculous threshold (like 98% coverage) will cause more damage that it will
help. Developers will have to start exposing aspects of the code that were
supposed to be encapsulated, just for the sake of reaching the necessary mark
needed for the pipeline to pass.&lt;/p&gt;
</content:encoded><category>javascript</category><category>code-coverage</category><category>jest</category><author>Elvis Adomnica</author></item><item><title>Simple fetch limiter (Part 2)</title><link>https://erranto.com/blog/rate-limiting-fetch-part-2</link><guid isPermaLink="true">https://erranto.com/blog/rate-limiting-fetch-part-2</guid><pubDate>Thu, 19 Oct 2023 16:28:08 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;img
style=&quot;margin: 0 auto; max-width: 95%;&quot;
src=&quot;https://media.tenor.com/2kAO6lk_cbIAAAAd/pointing-leonardo-di-caprio.gif&quot;
alt=&quot;Leonardo Dicaprio Reaction GIF&quot;
/&amp;gt;&lt;/p&gt;
&lt;p&gt;It is very possible that you had such a reaction while reading my
&lt;a href=&quot;/blog/fetch-rate-limit&quot;&gt;previous post&lt;/a&gt;, especially at the part where I
mentioned that moving the &lt;code&gt;delay&lt;/code&gt; to the bottom of the loop was a mistake.
The move at the bottom was an attempt to address an issue that becomes obvious
when using large values for the loop interval, e.g., 30 seconds.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const limiter = new HTTPLimiter({
  intervalMilliseconds: 30_000,
  requestsPerInterval: 2,
});

console.log(&quot;start&quot;);
for (let i = 10; i; i--) {
  limiter
    .fetch(`https://jsonplaceholder.typicode.com/todos/${i}`)
    .then((response) =&amp;gt; response.json())
    .then((json) =&amp;gt; console.log(json));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&apos;ll see &lt;code&gt;start&lt;/code&gt; being printed in the console, and only after the
30 seconds delay (sleep), we will see the requests being fired, and
the responses being printed as well. Let&apos;s move it back to the bottom of
the loop.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async #loop() {
  while (true) {
    const entities = this.#queue.splice(0, this.#options.requestsPerInterval);
    if (!entities.length) break;

    for (const entity of entities) {
      fetch(entity.input, entity.init)
        .then(entity.resolve)
        .catch(entity.reject);
    }

    await delay(this.#options.intervalMilliseconds);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We are now back at the issue that all the items are being consumed immediately.
Go back to the previous article for an explanation why that happens. In any case,
the fix is easy. All we have to do is to allow &quot;the producer&quot; (the main thread) to
produce all the requests it needs. For instance, allow our &lt;code&gt;todos&lt;/code&gt; fetch loop to
finish iterating over all the requests. We can easily achieve this by deferring
the start of processing the items to the next event loop cycle using &lt;code&gt;setTimeout&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fetch(input: FetchInput, init?: RequestInit): Promise&amp;lt;Response&amp;gt; {
  const promise = new Promise&amp;lt;Response&amp;gt;((resolve, reject) =&amp;gt; {
    this.#queue.push({
      input,
      init,
      resolve,
      reject,
    });
  });

  if (this.#queue.length === 1) setTimeout(() =&amp;gt; this.#loop(), 0);

  return promise;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now everything works as expected, and the code remained simple.&lt;/p&gt;
</content:encoded><category>typescript</category><category>async</category><category>fetch</category><category>rate-limit</category><category>delay</category><category>event-loop</category><author>Elvis Adomnica</author></item><item><title>Simple way to rate limit your fetch requests</title><link>https://erranto.com/blog/fetch-rate-limit</link><guid isPermaLink="true">https://erranto.com/blog/fetch-rate-limit</guid><pubDate>Tue, 17 Oct 2023 08:41:52 GMT</pubDate><content:encoded>&lt;p&gt;A few weeks ago, my colleagues at SprintEins shared &lt;a href=&quot;https://dev.to/sevapp/gentle-promise-based-http-client-for-deno-and-nodejs-part-2-1ian&quot;&gt;an
article&lt;/a&gt;
describing a solution for rate limiting fetch requests in the Deno to avoid
reaching an API rate limit. I used to write similar code in the past, so this
really caught my attention.&lt;/p&gt;
&lt;h2&gt;Original implementation&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/sevapp/fetchify/blob/0.0.1/src/HTTPLimiter.ts&quot;&gt;original
implementation&lt;/a&gt;
described in the &lt;a href=&quot;https://dev.to/sevapp/apis-fetch-and-deno-or-how-i-make-rate-limiter-1ch0&quot;&gt;first
part&lt;/a&gt;
of the artile series proposes an initial &quot;not production ready&quot; solution:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { delay } from &quot;https://deno.land/std@0.202.0/async/delay.ts&quot;;

interface IMyRequest {
  url: string;
  resolve: (value: Response | Error) =&amp;gt; void;
}

const queue: IMyRequest[] = [];

const myFetch = (url: string) =&amp;gt; {
  const promise = new Promise((resolve) =&amp;gt; {
    queue.push({
      url,
      resolve,
    });
  });

  return promise;
};

const loop = async (interval: number) =&amp;gt; {
  while (true) {
    await delay(interval);
    const req = queue.shift();
    if (!req) continue;

    try {
      const response = await fetch(req.url);
      req.resolve(response);
    } catch (error) {
      req.resolve(error);
    }
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The self criticism the author brings, and the reason they call it &quot;not
production code&quot;, is that the code doesn&apos;t respect the &lt;code&gt;fetch&lt;/code&gt; interface.
However, &lt;a href=&quot;https://github.com/sevapp/fetchify/blob/0.0.1/src/HTTPLimiter.ts&quot;&gt;the version from
Github&lt;/a&gt; linked
in the second article fixes this by pushing &lt;code&gt;FetchInput&lt;/code&gt; and &lt;code&gt;RequestInit&lt;/code&gt;
parameters into the queue:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fetch(input: FetchInput, init?: RequestInit): Promise&amp;lt;Response&amp;gt; {
  let promise = undefined as unknown as Promise&amp;lt;Response&amp;gt;;

  promise = new Promise((resolve, reject) =&amp;gt; {
    this.#queue.push({
      promise,
      input,
      init,
      resolve,
      reject,
    });
  });

  if (this.#queue.length === 1) this.#loop();

  return promise;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After a quick glance at the entire code, one can notice that the &lt;code&gt;promise&lt;/code&gt; field
of the &lt;code&gt;IRequestEntity&lt;/code&gt; is never used, so &lt;code&gt;fetch&lt;/code&gt; could be simplified to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fetch(input: FetchInput, init?: RequestInit): Promise&amp;lt;Response&amp;gt; {
  const promise = new Promise((resolve, reject) =&amp;gt; {
    this.#queue.push({
      input,
      init,
      resolve,
      reject,
    });
  });

  if (this.#queue.length === 1) this.#loop();

  return promise;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The private &lt;code&gt;#loop&lt;/code&gt; method has also been slightly changed from its original
proposal:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async #loop() {
  while (true) {
    const entity = this.#queue.shift();
    if (!entity) break;

    try {
      const response = await fetch(entity.input, entity.init);
      entity.resolve(response);
    } catch (error) {
      entity.reject(error);
    }

    await delay(this.#options.interval);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Moving the &lt;code&gt;delay&lt;/code&gt; at the end of the &lt;code&gt;#loop&lt;/code&gt; method is actually a mistake. The
following code executes the fetch requests immediately:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const limiter = new HTTPLimiter({
  interval: 1000,
});

for (let i = 10; i--; ) {
  console.log(i);
  await limiter.fetch(`https://jsonplaceholder.typicode.com/todos/${i}`);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is because, as soon as we push the item into the queue, &lt;code&gt;this.#queue.length === 1&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;, and we invoke the &lt;code&gt;#loop&lt;/code&gt; method. We then pop the item from the
queue and we proceed with the request. Processing the item immediately means
that &lt;code&gt;this.#queue.length === 1&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt; for every &lt;code&gt;limiter.fetch&lt;/code&gt; call, thus
creating a new &lt;code&gt;while(true)&lt;/code&gt; loop each time. Each of these loops will then wait
for the delay and will be interrupted in the next cycle due to the &lt;code&gt;if (!entity) break;&lt;/code&gt; statement. Moving back the &lt;code&gt;delay&lt;/code&gt; function at the beginning of the
&lt;code&gt;while&lt;/code&gt; loop will block it for at least 1 second before it continues with
processing the items. This behavior is closer to what we need. If we remove the
&lt;code&gt;await&lt;/code&gt; in front of the &lt;code&gt;limiter.fetch&lt;/code&gt; call, we will notice that all requests
get queued and then processed one-by-one with 1 second delay between them.&lt;/p&gt;
&lt;h2&gt;Extended version&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://dev.to/sevapp/gentle-promise-based-http-client-for-deno-and-nodejs-part-2-1ian&quot;&gt;The second
part&lt;/a&gt;
of the series goes in a direction I don&apos;t quite agree with. Instead, I would
rename the &lt;code&gt;interval&lt;/code&gt; option to &lt;code&gt;intervalMilliseconds&lt;/code&gt;, add a new option called
&lt;code&gt;requestsPerInterval&lt;/code&gt;, and update the logic correspondingly. Here is the entire
&lt;code&gt;HTTPLimiter.ts&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { delay } from &quot;../deps.ts&quot;;

interface IRequestEntity {
  init?: RequestInit;
  input: FetchInput;
  resolve: (value: Response) =&amp;gt; void;
  reject: (value: Error) =&amp;gt; void;
}

interface IHTTPLimiterOptions {
  intervalMilliseconds: number;
  requestsPerInterval: number;
}

type FetchInput = URL | Request | string;

export class HTTPLimiter {
  #options: IHTTPLimiterOptions = {
    intervalMilliseconds: 0,
    requestsPerInterval: 0,
  };

  #queue: IRequestEntity[] = [];

  constructor(options?: Partial&amp;lt;IHTTPLimiterOptions&amp;gt;) {
    if (options) {
      for (const [key, value] of Object.entries(options)) {
        // @ts-ignore
        if (key in this.#options) this.#options[key] = value;
      }
    }
  }

  async #loop() {
    while (true) {
      await delay(this.#options.intervalMilliseconds);
      const entities = this.#queue.splice(0, this.#options.requestsPerInterval);
      if (!entities.length) break;

      for (const entity of entities) {
        try {
          const response = await fetch(entity.input, entity.init);
          entity.resolve(response);
        } catch (error) {
          entity.reject(error);
        }
      }
    }
  }

  fetch(input: FetchInput, init?: RequestInit): Promise&amp;lt;Response&amp;gt; {
    const promise = new Promise&amp;lt;Response&amp;gt;((resolve, reject) =&amp;gt; {
      this.#queue.push({
        input,
        init,
        resolve,
        reject,
      });
    });

    if (this.#queue.length === 1) this.#loop();

    return promise;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and an example how to test it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { HTTPLimiter } from &quot;./src/HTTPLimiter.ts&quot;;

const limiter = new HTTPLimiter({
  intervalMilliseconds: 1000,
  requestsPerInterval: 2,
});

for (let i = 10; i; i--) {
  limiter
    .fetch(`https://jsonplaceholder.typicode.com/todos/${i}`)
    .then((response) =&amp;gt; response.json())
    .then((json) =&amp;gt; console.log(json));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we are executing the code above, we will notice that only two requests are
being fired every second, until all 10 requests are done. We still have a
problem though. As pointed out in the series, our &lt;code&gt;fetch&lt;/code&gt; requests are being
executed sequentially. To better visualize this, we can use the following example code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { HTTPLimiter } from &quot;./src/HTTPLimiter.ts&quot;;

const limiter = new HTTPLimiter({
  intervalMilliseconds: 1000,
  requestsPerInterval: 2,
});

let lastTime = new Date().getTime();
let totalTime = 0;

for (let i = 10; i; i--) {
  limiter
    .fetch(`https://jsonplaceholder.typicode.com/todos/${i}`)
    .then((response) =&amp;gt; response.json())
    .then((json) =&amp;gt; {
      const now = new Date().getTime();
      const delta = now - lastTime;
      totalTime += delta;

      console.log(totalTime, delta, json);

      lastTime = now;
    });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and, we can also mock the global &lt;code&gt;fetch&lt;/code&gt; to control the time it takes to resolve a request:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const fetch = (input, _) =&amp;gt;
  new Promise&amp;lt;Response&amp;gt;((resolve) =&amp;gt;
    setTimeout(() =&amp;gt; {
      const response = {
        json: () =&amp;gt; new Promise&amp;lt;Response&amp;gt;((res) =&amp;gt; res({ input } as any)),
      } as Response;

      resolve(response);
    }, 300)
  );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code returns a &lt;code&gt;Promise&lt;/code&gt; that looks like a &lt;code&gt;Response&lt;/code&gt; (has a &lt;code&gt;json&lt;/code&gt; method which
returns a &lt;code&gt;Promise&lt;/code&gt;) and resolves after 300ms. Running it should output something similar to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ deno run --allow-net example.ts
1306 1306 { input: &quot;https://jsonplaceholder.typicode.com/todos/10&quot; }
1607 301 { input: &quot;https://jsonplaceholder.typicode.com/todos/9&quot; }
2910 1303 { input: &quot;https://jsonplaceholder.typicode.com/todos/8&quot; }
3211 301 { input: &quot;https://jsonplaceholder.typicode.com/todos/7&quot; }
4514 1303 { input: &quot;https://jsonplaceholder.typicode.com/todos/6&quot; }
4816 302 { input: &quot;https://jsonplaceholder.typicode.com/todos/5&quot; }
6119 1303 { input: &quot;https://jsonplaceholder.typicode.com/todos/4&quot; }
6420 301 { input: &quot;https://jsonplaceholder.typicode.com/todos/3&quot; }
7724 1304 { input: &quot;https://jsonplaceholder.typicode.com/todos/2&quot; }
8026 302 { input: &quot;https://jsonplaceholder.typicode.com/todos/1&quot; }

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s around 8 seconds instead of around 5. Not good. Let&apos;s now fix this
by making our loop run the requests in parallel:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async #loop() {
  while (true) {
    await delay(this.#options.intervalMilliseconds);
    const entities = this.#queue.splice(0, this.#options.requestsPerInterval);
    if (!entities.length) break;

    for (const entity of entities) {
      fetch(entity.input, entity.init)
        .then(entity.resolve)
        .catch(entity.reject);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Indeed, this solution yields the desired results, as you can see below.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ deno run --allow-net example.ts
1307 1307 { input: &quot;https://jsonplaceholder.typicode.com/todos/10&quot; }
1315 8 { input: &quot;https://jsonplaceholder.typicode.com/todos/9&quot; }
2308 993 { input: &quot;https://jsonplaceholder.typicode.com/todos/8&quot; }
2309 1 { input: &quot;https://jsonplaceholder.typicode.com/todos/7&quot; }
3310 1001 { input: &quot;https://jsonplaceholder.typicode.com/todos/6&quot; }
3311 1 { input: &quot;https://jsonplaceholder.typicode.com/todos/5&quot; }
4312 1001 { input: &quot;https://jsonplaceholder.typicode.com/todos/4&quot; }
4313 1 { input: &quot;https://jsonplaceholder.typicode.com/todos/3&quot; }
5314 1001 { input: &quot;https://jsonplaceholder.typicode.com/todos/2&quot; }
5315 1 { input: &quot;https://jsonplaceholder.typicode.com/todos/1&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yey, we managed to reached our goal of having a fully functional API-rate-limiter implementation
with just a few simple changes to the original code!&lt;/p&gt;
</content:encoded><category>typescript</category><category>api-rate-limit</category><category>async</category><category>fetch</category><category>rate-limit</category><category>request</category><author>Elvis Adomnica</author></item><item><title>Social media cleansing</title><link>https://erranto.com/blog/social-media-cleansing</link><guid isPermaLink="true">https://erranto.com/blog/social-media-cleansing</guid><pubDate>Fri, 16 Dec 2022 16:18:58 GMT</pubDate><content:encoded>&lt;p&gt;I remember the early days of social media interaction when I would got to an internet cafe
just to have a chat on mIRC with someone from my community. If I was lucky, I would even get to
have a blind date. Those years seem like ages ago and a lot has changed, gone are the days
of mIRC, HI5, MySpace, and Yahoo Messenger.&lt;/p&gt;
&lt;p&gt;I&apos;ve never been a big fan of Twitter so my social media activity revolved around Facebook,
Facebook Messenger and Instagram. With Instagram I had a somehow healthy relationship. Since
I have joined the platform back in 2016, I have decided to only post pictures that have a
meaningful story behind them. That usually meant I will only post a few times a year. But still,
here I was checking the app multiple times a day and knowing almost all the stories posted by the
people I follow. Not the best use of my time and a somehow addictive behavior.&lt;/p&gt;
&lt;p&gt;This year, on the other hand, has been a time for change and part of it has been tying to disconnect
from all the noise and distractions that consume my time. In June I have deleted Facebook and Facebook
Messenger, and this month I have deleted my main Instagram account (I still have an &quot;arty&quot; one). In the
beginning I felt like a void, like something was missing. But after a while that void feels like a
relief. I get more time to focus on the things that matter and have meaningful the conversations I so much
enjoy. I totally recommend it :-)&lt;/p&gt;
</content:encoded><category>personal</category><author>Elvis Adomnica</author></item><item><title>Hello, World!</title><link>https://erranto.com/blog/hello-world</link><guid isPermaLink="true">https://erranto.com/blog/hello-world</guid><pubDate>Wed, 14 Dec 2022 20:17:09 GMT</pubDate><content:encoded>&lt;p&gt;This is my first blog post!&lt;/p&gt;
</content:encoded><category>erranto</category><author>Elvis Adomnica</author></item></channel></rss>