<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Untitled Publication]]></title><description><![CDATA[Untitled Publication]]></description><link>https://blog.xnim.me</link><generator>RSS for Node</generator><lastBuildDate>Tue, 21 Apr 2026 13:01:52 GMT</lastBuildDate><atom:link href="https://blog.xnim.me/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Things I wish TypeScript had. Part 1]]></title><description><![CDATA[Note: these features are not in TypeScript (Aug 2024), that's a "what if" sort of posts about the things which I wish TS had, because on my daily basis I face some difficulties because of these tiny things.
Examples in that article are artificial and...]]></description><link>https://blog.xnim.me/things-i-wish-typescript-had-part-1</link><guid isPermaLink="true">https://blog.xnim.me/things-i-wish-typescript-had-part-1</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Nik]]></dc:creator><pubDate>Sun, 11 Aug 2024 13:14:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1723382661780/7a5f71da-03b5-48a6-8126-4041c058f116.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Note: these features are not in TypeScript (Aug 2024), that's a "what if" sort of posts about the things which I wish TS had, because on my daily basis I face some difficulties because of these tiny things.</em></p>
<p><em>Examples in that article are artificial and not how TS works at the moment.</em></p>
<h1 id="heading-throws-syntax">Throws syntax</h1>
<p>When you are working on utility components, infra, libraries: you need a clear API.</p>
<p>What is the clear API? It's not just input=&gt;output model but also being clear about potential mis-uses. And that's where throws syntax comes in handy:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Non-real code</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">foo</span>(<span class="hljs-params">x: <span class="hljs-built_in">number</span></span>): <span class="hljs-title">number</span> / <span class="hljs-title">throws</span> <span class="hljs-title">TypeError</span> </span>{
  <span class="hljs-keyword">if</span> (x &lt; <span class="hljs-number">0</span>) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">TypeError</span>(<span class="hljs-string">`Expected positive number, got <span class="hljs-subst">${x}</span>`</span>);
  }
  ...
}
</code></pre>
<p>That syntax looks verbose, yet it fulfils the main goal, now, when you would use that function you actually would be required to handle that potential case. (<a target="_blank" href="https://www.w3schools.com/java/ref_keyword_throws.asp">Example from Java</a>). Or, well, at least you get better informed!</p>
<p>That approach saves an engineer tons of time. Imagine:</p>
<ol>
<li><p>You use a new library in project. And to understand all errors to handle you have to go either to documentation or library internals</p>
</li>
<li><p>Use code your colleague wrote</p>
</li>
<li><p>Use code you wrote years ago</p>
</li>
</ol>
<h2 id="heading-workaround">Workaround?</h2>
<p>Since we don't have that support, what can we do to ensure the same level or even better level?</p>
<h3 id="heading-either-monad">Either monad</h3>
<p>In vast majority of cases Either monad can be the best fit. Here you can find a <a target="_blank" href="https://www.youtube.com/watch?v=SLOhXSeNKCM&amp;list=PL37ZVnwpeshEczPCbFGVGd-hj1DR7_SKy&amp;index=13">wonderful talk</a> of using Either monad from friends of mine Dmitry and Artem.</p>
<p>In short, the example above can be replaced with:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Real code</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">foo</span>(<span class="hljs-params">x: <span class="hljs-built_in">number</span></span>): <span class="hljs-title">Either</span>&lt;<span class="hljs-title">TypeError</span>, <span class="hljs-title">Number</span>&gt; </span>{
  <span class="hljs-keyword">if</span> (x &lt; <span class="hljs-number">0</span>) {
    <span class="hljs-keyword">return</span> Either.left(<span class="hljs-keyword">new</span> <span class="hljs-built_in">TypeError</span>(<span class="hljs-string">`Expected positive number, got <span class="hljs-subst">${x}</span>`</span>));
  }
  ...
  <span class="hljs-keyword">return</span> Either.right(returnValue);
}
</code></pre>
<p>Pros:</p>
<p>➕ <code>Either</code> allows you to separate "expected" or "designed" errors from Runtime issues.</p>
<p>➕ <code>Either</code> guarantees that an engineer will be required to handle, or at least to be informed of the potential non-happy path.</p>
<p>Cons:</p>
<p>➖ It's highly likely <code>Either</code> underneath is using Object / Class / etc. That means, you will have an additional object to process which lifetime is not that long. Creating tons of objects makes GC calls more frequent and hence slows down your application. Death by thousand cuts.</p>
<p>➖There is no native <code>Either</code> monad. You shall use 3rd party library or create your own version.</p>
<p>So, unless you target to have a high-performance application, <code>Either</code> can be a good choice to use in your application. 👍</p>
<h1 id="heading-typed-catch-syntax">Typed catch syntax</h1>
<p>If something throws errors I want powerful tools to handle them.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Non-real code</span>
<span class="hljs-keyword">try</span> { 
    <span class="hljs-keyword">await</span> foo() 
} <span class="hljs-keyword">catch</span> { 
    <span class="hljs-comment">// Remember, foo can throw TypeError, right? </span>
    <span class="hljs-keyword">case</span> (e: <span class="hljs-built_in">TypeError</span>) {
        ...
    } 
    <span class="hljs-comment">// Generic Error </span>
    <span class="hljs-keyword">case</span> (e: <span class="hljs-built_in">Error</span>) {
        ...
    } 
    <span class="hljs-comment">// Plain string error </span>
    <span class="hljs-keyword">case</span> (e: <span class="hljs-built_in">string</span>) {
        ...
    } <span class="hljs-keyword">default</span> (e) { <span class="hljs-comment">// e is any </span>
        ...
    } 
}
</code></pre>
<p>Why?</p>
<ol>
<li><p>Simple wrapper (you can manage to have something similar even now, but the code will be significantly more verbose).</p>
</li>
<li><p>It's easy to transpile to JS (think of native switch-case inside catch)</p>
</li>
<li><p>Language has capabilities to perform the check in most of cases we need</p>
<ol>
<li>Yet, these limitations should be based on runtime "types" using <code>instanceof</code>/<code>typeof</code> checks</li>
</ol>
</li>
<li><p>Makes code structured</p>
</li>
</ol>
<h2 id="heading-workaround-1">Workaround?</h2>
<p>As we are speaking of syntax sugar here, we can easily replace it with similar structure:</p>
<h3 id="heading-using-switch-case">Using Switch-case:</h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// Real code</span>
<span class="hljs-keyword">try</span> { 
    <span class="hljs-keyword">await</span> foo() 
} <span class="hljs-keyword">catch</span> (e: <span class="hljs-built_in">any</span>) { 
    <span class="hljs-keyword">switch</span> (<span class="hljs-literal">true</span>) {
        <span class="hljs-keyword">case</span> e <span class="hljs-keyword">instanceof</span> <span class="hljs-built_in">TypeError</span>: {
            <span class="hljs-comment">// e is TypeError          </span>
            ...
            <span class="hljs-keyword">break</span>;
        }
        <span class="hljs-keyword">case</span> e <span class="hljs-keyword">instanceof</span> <span class="hljs-built_in">Error</span>: {
            <span class="hljs-comment">// e is Error</span>
            ...
            <span class="hljs-keyword">break</span>;
        }
        <span class="hljs-keyword">case</span> <span class="hljs-keyword">typeof</span> e === <span class="hljs-string">'string'</span>: {
            <span class="hljs-comment">// e is string</span>
            ...
            <span class="hljs-keyword">break</span>;
        }
        <span class="hljs-keyword">default</span>: {
            ...
        }
    }    
}
</code></pre>
<p>Pros:</p>
<p>➕ Great TS support, in each switch-case <code>e</code> type will be correctly defined!</p>
<p>➕ No need for additional library, transitions.</p>
<p>➕ Everything works out of the box</p>
<p>Cons:</p>
<p>➖ Verbose</p>
<h3 id="heading-using-if-else">Using if-else:</h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// Real code</span>
<span class="hljs-keyword">try</span> { 
    <span class="hljs-keyword">await</span> foo() 
} <span class="hljs-keyword">catch</span> (e: <span class="hljs-built_in">any</span>) { 
    <span class="hljs-keyword">if</span> (e <span class="hljs-keyword">instanceof</span> <span class="hljs-built_in">TypeError</span>) {
        ...
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (e <span class="hljs-keyword">instanceof</span> <span class="hljs-built_in">Error</span>) {
        ...
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> e === <span class="hljs-string">'string'</span>) {
        ...
    } <span class="hljs-keyword">else</span> {
        ...
    }     
}
</code></pre>
<p>That example looks cleaner, but has some common pitfalls, where the main one is <strong>You have to define if-else chain or early return from each of the case.</strong></p>
<p>If we don't do that, we risk to get into <code>e instanceof TypeError</code> and then immediately to <code>e instanceof Error</code> as Error is base (generic) Error class.</p>
<p>If you define your own custom error, it's highly likely that Error class will extend base <code>Error</code> too.</p>
<h3 id="heading-using-either-monad">Using Either monad</h3>
<p>In that case, we can separate Runtime errors and designed ones:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Real code</span>
<span class="hljs-keyword">declare</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">foo</span>(<span class="hljs-params"></span>): <span class="hljs-title">Either</span>&lt;<span class="hljs-title">TypeError</span> | <span class="hljs-title">string</span>, <span class="hljs-title">number</span>&gt;</span>;

<span class="hljs-keyword">try</span> { 
    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> foo();
    <span class="hljs-keyword">if</span> (result.left) {
        <span class="hljs-comment">// switch-case or if for TypeError / string</span>
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-comment">// result.right is number</span>
    }
} <span class="hljs-keyword">catch</span> (e: <span class="hljs-built_in">any</span>) { 
    <span class="hljs-comment">// e is highly likely just a runtime error meaning:</span>
    <span class="hljs-comment">// e instanceof Error === true</span>
}
</code></pre>
<p>Pros:</p>
<p>➕ Errors are separated between expected/designed and runtime</p>
<p>Cons:</p>
<p>➖ Verbose (can be simplified, depending on Either library choice)</p>
<p>➖ Requires 3rd party library / own solution</p>
<h1 id="heading-can-we-finally-have-fully-supported-map-syntax">Can we finally have fully-supported Map syntax</h1>
<p><em>Please, please, please!!</em> That's something which is discussed in TypeScript community quite a lot. E.g.: <a target="_blank" href="https://github.com/microsoft/TypeScript/issues/13086">https://github.com/microsoft/TypeScript/issues/13086</a></p>
<p>People already wrote tons of workaround for that but the truth is that many workarounds are fragile. And they might be easily broken.</p>
<p>Just make proper .has =&gt; .get support already!</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Real code</span>
<span class="hljs-keyword">const</span> map = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">string</span>, <span class="hljs-built_in">number</span>&gt;();
map.set(<span class="hljs-string">'foo'</span>, <span class="hljs-number">1</span>);
map.set(<span class="hljs-string">'bar'</span>, <span class="hljs-number">2</span>);

<span class="hljs-keyword">if</span> (map.has(<span class="hljs-string">'foo'</span>)) {
    <span class="hljs-comment">// TS error, value is number|undefined 👎</span>
    <span class="hljs-keyword">const</span> value: <span class="hljs-built_in">number</span> = map.get(<span class="hljs-string">'foo'</span>);
}

<span class="hljs-keyword">if</span> (map.has(<span class="hljs-string">'foo'</span>)) {
    <span class="hljs-comment">// TS is happy but what the price! 👎</span>
    <span class="hljs-keyword">const</span> value: <span class="hljs-built_in">number</span> | <span class="hljs-literal">undefined</span> = map.get(<span class="hljs-string">'foo'</span>);
}

<span class="hljs-comment">// Workaround 🤮</span>
<span class="hljs-keyword">const</span> value = map.get(<span class="hljs-string">'foo'</span>);
<span class="hljs-keyword">if</span> (value != <span class="hljs-literal">null</span>) {
    <span class="hljs-comment">// value: number</span>
    <span class="hljs-keyword">const</span> result = value + <span class="hljs-number">123</span>;
}
</code></pre>
<h2 id="heading-workaround-2">Workaround?</h2>
<h3 id="heading-use-just-get"><strong>Use just</strong> <code>get</code><strong>:</strong></h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// Real code</span>
<span class="hljs-keyword">const</span> value = map.get(<span class="hljs-string">'foo'</span>);
<span class="hljs-keyword">if</span> (value != <span class="hljs-literal">null</span>) {
    <span class="hljs-comment">// value: number</span>
    <span class="hljs-keyword">const</span> result = value + <span class="hljs-number">123</span>;
}
</code></pre>
<p>Pros:</p>
<p>➕ Simply</p>
<p>➕ Works</p>
<p>Cons:</p>
<p>➖ I still want <code>has</code> + <code>get</code> 😭</p>
<h3 id="heading-patch-map-interface"><strong>Patch map interface</strong></h3>
<p>Borrowed from <a target="_blank" href="https://github.com/microsoft/TypeScript/issues/13086#issuecomment-269694547">issue</a>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> x = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">string</span>, <span class="hljs-built_in">string</span>&gt;();

<span class="hljs-keyword">interface</span> Map&lt;K, V&gt; {
    has&lt;CheckedString <span class="hljs-keyword">extends</span> <span class="hljs-built_in">string</span>&gt;(<span class="hljs-built_in">this</span>: <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">string</span>, V&gt;, key: CheckedString): <span class="hljs-built_in">this</span> is MapWith&lt;K, V, CheckedString&gt;
}

<span class="hljs-keyword">interface</span> MapWith&lt;K, V, DefiniteKey <span class="hljs-keyword">extends</span> K&gt; <span class="hljs-keyword">extends</span> Map&lt;K, V&gt; {
    get(k: DefiniteKey): V;
    get(k: K): V | <span class="hljs-literal">undefined</span>;
}

x.set(<span class="hljs-string">"key"</span>, <span class="hljs-string">"value"</span>);
<span class="hljs-keyword">if</span> (x.has(<span class="hljs-string">"key"</span>)) {
  <span class="hljs-keyword">const</span> a: <span class="hljs-built_in">string</span> = x.get(<span class="hljs-string">"key"</span>); <span class="hljs-comment">// works!</span>
}
</code></pre>
<p>Pros:</p>
<p>➕ Handles what I want 😍</p>
<p>Cons:</p>
<p>➖ I don't recommend that 😒</p>
<p>➖ If we add delete between has &amp; get, TS still will be happy, while we introduce potential bug. So, unless you are confident you handle all corner cases... Just look at that example:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> x = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">string</span>, <span class="hljs-built_in">string</span>&gt;();

<span class="hljs-keyword">interface</span> Map&lt;K, V&gt; {
    has&lt;CheckedString <span class="hljs-keyword">extends</span> <span class="hljs-built_in">string</span>&gt;(<span class="hljs-built_in">this</span>: <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">string</span>, V&gt;, key: CheckedString): <span class="hljs-built_in">this</span> is MapWith&lt;K, V, CheckedString&gt;
}

<span class="hljs-keyword">interface</span> MapWith&lt;K, V, DefiniteKey <span class="hljs-keyword">extends</span> K&gt; <span class="hljs-keyword">extends</span> Map&lt;K, V&gt; {
    get(k: DefiniteKey): V;
    get(k: K): V | <span class="hljs-literal">undefined</span>;
}

x.set(<span class="hljs-string">"key"</span>, <span class="hljs-string">"value"</span>);
<span class="hljs-keyword">if</span> (x.has(<span class="hljs-string">"key"</span>)) {
  x.delete(<span class="hljs-string">"key"</span>); <span class="hljs-comment">// We manually delete an item</span>
  <span class="hljs-keyword">const</span> a: <span class="hljs-built_in">string</span> = x.get(<span class="hljs-string">"key"</span>); <span class="hljs-comment">// TS is happy, we have issue :(</span>
}
</code></pre>
<p>^-- That means, that the whole Map typing should be modified to handle that and other corner cases.</p>
<p><em>So, I'd prefer the proper solution to be the part of standard TS library.</em></p>
<h1 id="heading-module-package-level-class-access-control">Module / Package-level class access control</h1>
<p>Sometimes, you have a class and some amount of internal methods which should be accessed inside the same package/module. Like.. they are not private, but "module-visible". <a target="_blank" href="https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html">Example in java</a></p>
<p>MyClass.js:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Non-real code</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> MyClass { 
    <span class="hljs-keyword">public</span> foo() { 
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'foo'</span>); 
    } 
    <span class="hljs-keyword">private</span> bar() { 
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'bar'</span>); 
    } 
    <span class="hljs-keyword">module</span> baz() { 
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'baz'</span>) 
    } 
}

<span class="hljs-keyword">const</span> test = <span class="hljs-keyword">new</span> MyClass(); 
test.foo() <span class="hljs-comment">// ok </span>
test.bar() <span class="hljs-comment">// error </span>
test.baz() <span class="hljs-comment">// Same module, OK</span>
</code></pre>
<p>Another file.js</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Another module </span>
<span class="hljs-keyword">import</span> {MyClass} <span class="hljs-keyword">from</span> <span class="hljs-string">'./MyClass'</span>;
<span class="hljs-keyword">const</span> test = <span class="hljs-keyword">new</span> MyClass();
test.foo() <span class="hljs-comment">// ok </span>
test.bar() <span class="hljs-comment">// error, private</span>
test.baz() <span class="hljs-comment">// Anotehr module, ERROR</span>
</code></pre>
<p>Why?</p>
<ul>
<li><p>Libraries / utils, which have internal behaviour</p>
</li>
<li><p>Clear Public API without excess methods</p>
</li>
<li><p>module-level methods inside the class will have clear access to private fields defined with <code>#</code> notation.</p>
<ul>
<li>Remember, that TypeScript access modifiers are syntax enhancements, while <code>#</code> notation defines really private fields</li>
</ul>
</li>
</ul>
<h2 id="heading-workaround-3">Workaround?</h2>
<h3 id="heading-interfaces">Interfaces</h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// Real code</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> IMyClass {
  foo(): <span class="hljs-built_in">void</span>;
}
<span class="hljs-keyword">class</span> MyClass <span class="hljs-keyword">implements</span> IMyClass { 
    <span class="hljs-keyword">public</span> foo() { 
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'foo'</span>); 
    } 
    <span class="hljs-keyword">private</span> bar() { 
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'bar'</span>); 
    } 
    baz() { 
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'baz'</span>) 
    } 
}
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">makeMyClass</span>(<span class="hljs-params"></span>): <span class="hljs-title">IMyClass</span> </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> MyClass;
}

<span class="hljs-comment">// Inside the same module we can use class creation through `new`:</span>
<span class="hljs-keyword">const</span> test = <span class="hljs-keyword">new</span> MyClass(); 
test.foo() <span class="hljs-comment">// ok </span>
test.bar() <span class="hljs-comment">// error </span>
test.baz() <span class="hljs-comment">// public, ok</span>
</code></pre>
<p>Another file.js</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Another module </span>
<span class="hljs-keyword">import</span> {makeMyClass} <span class="hljs-keyword">from</span> <span class="hljs-string">'./MyClass'</span>;
<span class="hljs-comment">// External module does not have direct access to MyClass</span>
<span class="hljs-keyword">const</span> test = makeMyClass(); <span class="hljs-comment">// test is IMyClass</span>
test.foo() <span class="hljs-comment">// ok </span>
test.bar() <span class="hljs-comment">// error, not defined in interface</span>
test.baz() <span class="hljs-comment">// error, not defined in interface</span>
</code></pre>
<p>Pros:</p>
<p>➕ Clear public API</p>
<p>Cons:</p>
<p>➖ A little bit too "boilerplatish"</p>
]]></content:encoded></item><item><title><![CDATA[Understanding IndexedDB. The complete guide.]]></title><description><![CDATA[Before we start
I have been working with IndexedDB for more than 3 years. When I just started, I thought, huh, it's similar to MongoDB, easy-peasy. Quite fast I understood that:

The native API is not that easy to use, you need a handy wrapper like D...]]></description><link>https://blog.xnim.me/indexeddb-guide</link><guid isPermaLink="true">https://blog.xnim.me/indexeddb-guide</guid><category><![CDATA[indexeddb]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Nik]]></dc:creator><pubDate>Tue, 12 Dec 2023 13:00:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1702251782848/59c6ac51-4a05-40cf-9509-b81c2f0793eb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-before-we-start">Before we start</h3>
<p>I have been working with IndexedDB for more than 3 years. When I just started, I thought, huh, it's similar to MongoDB, easy-peasy. Quite fast I understood that:</p>
<ol>
<li><p>The native API is not <em>that</em> easy to use, you need a handy wrapper like <a target="_blank" href="https://dexie.org/">Dexie</a> or <a target="_blank" href="https://github.com/jakearchibald/idb">idb with Promises</a> from Jake Archibald</p>
</li>
<li><p>It's quite easy to make IndexedDB slow, <em>especially if you're using Dexie, but that's a separate story.</em></p>
</li>
<li><p>Transactions are fragile</p>
</li>
<li><p>and many other things</p>
</li>
</ol>
<p>I used native iDB API, prepared the typing for iDB in flow (yup, I'm working in Meta), worked with Dexie and even worked on my own ORM. I kept finding some tricky yet interesting insights about IndexedDB.</p>
<p>This article is a complete "What do I know about IndexedDB" guide.</p>
<p>Get your coffee, bookmark the page, share / retweet!</p>
<p><em>❗️ All the</em> <em>examples</em> <em>are available in this</em> <a target="_blank" href="https://codesandbox.io/p/sandbox/indexeddb-examples-5f4lms?file=%2Fsrc%2FdbConnection.ts%3A16%2C9"><em>codesandbox</em></a> <em>or</em> <a target="_blank" href="https://github.com/xnimorz/indexedDB-examples"><em>github repo</em></a><em>.</em></p>
<h2 id="heading-indexeddb">IndexedDB:</h2>
<ol>
<li><p>is NoSQL and represents key-value storage DB;</p>
</li>
<li><p>don't have fixed schema for ObjectStores:</p>
<ol>
<li>You can save something like: <code>1: {foo: 'bar'}, 2: {hello: 123}, 'world': {bar: {baz: ['hello', 123', 'world']}}</code> where 1,2, and 'world' are primary keys;</li>
</ol>
</li>
<li><p>has indexed access. If you want to find the data by key, the key should be defined beforehand. If you try to filter data by non-indexed field you will receive an error:</p>
<ol>
<li><em>Note: you can read all the records or iterate through all the records but it will be extremely slow (O(n));</em></li>
</ol>
</li>
<li><p>is transaction based. You cannot access (neither read nor write) the DB outside the transaction;</p>
</li>
<li><p>is async by design. Original IndexedDB API is callback-based, however, there are tons of promise-based wrappers;</p>
</li>
<li><p>supports the work with blobs, binary formats (ArrayBuffers and so on);</p>
</li>
<li><p>has <a target="_blank" href="https://github.com/google/leveldb">LevelDB</a> as a backend for iDB <a target="_blank" href="https://chromium.googlesource.com/chromium/src/+/master/content/browser/indexed_db/docs/README.md#leveldb-database">in Chrome</a> and hence for V8-based browsers.</p>
</li>
</ol>
<h2 id="heading-the-structure-of-indexeddb">The structure of IndexedDB:</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701613110416/ac2900dc-8fd0-4a5d-84fe-2a8c856f4c88.png" alt class="image--center mx-auto" /></p>
<p>IndexedDB consists of:</p>
<ol>
<li><p>Database: the top level of IndexedDB. Each Database contains:</p>
<ol>
<li><p>Name: unique name of the DB for the definite domain;</p>
</li>
<li><p>Version: DB versions are used for DB migrations. DB migration is a special type of transaction, named "version change transaction";</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701618449044/0b8476be-a440-48ec-b60e-d34b42f6da71.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
</li>
<li><p>Object Store: a "table" in your Database. An object store consists of a list of records, addressed by your key path.</p>
<ol>
<li><p>In addition to your key, you can have several indexes. Every Index will point out to your primary key in the object store (to avoid data duplicity)</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701618711581/a25d2db6-abc9-49fe-9612-a7ead631d129.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
</li>
<li><p>Keys: as ObjectStore is key-value storage, keys are used as a unique path to your values (similar to primary key).<br /> Single key:</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701623141999/72dcc6a2-3d1a-446f-82b7-0c71f7d87160.png" alt class="image--center mx-auto" /></p>
<p> Compound keyPath:</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701623118901/0566bd6b-2524-49b1-9367-b9bb94b78bf1.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Indexes: each object store can have a list of many indexes associated with that store. Index refers to a list of fields of the objects in the store. Only the first level of objects is used.<br /> Indexes are used as a reference to a key, which allows IndexedDB to address the record itself.<br /> Index can be:</p>
<ol>
<li><p>Compound: consists of several fields</p>
</li>
<li><p>Single: indexes single field in ObjectStore</p>
</li>
<li><p>Unique: repetition is prohibited</p>
</li>
<li><p>MultiEntry: support array indexation</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701623012810/4d102af8-b65a-4e4f-8f5c-82f71ed89e52.png" alt class="image--center mx-auto" /></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701623034606/0e908826-85f2-4e3d-aad4-c6bd8ae3504b.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
</li>
</ol>
<h2 id="heading-indexeddb-cheatsheet-schema">IndexedDB cheatsheet schema:</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1702227885207/4b56939f-deca-4b1b-bde0-ebcf9da77866.png" alt class="image--center mx-auto" /></p>
<p>Link: <a target="_blank" href="https://excalidraw.com/#json=fCE1ViGCCh-cmWt3vwLUw,6ruYd_5L1F-_dehB7XQAkQ">https://excalidraw.com/#json=fCE1ViGCCh-cmWt3vwLUw,6ruYd_5L1F-_dehB7XQAkQ</a></p>
<h2 id="heading-how-to-create-database">How to: create database</h2>
<p>There is no separate API just to "create" a database. Everything starts with a connection. <code>indexedDB.open</code> creates a connection to the database. If there is no such database, it will be created.</p>
<p><code>indexedDB.open</code> receives:</p>
<ul>
<li><p>DB name: required field to identify the DB you want to be connected to</p>
</li>
<li><p>version: the version of the DB. This field is optional. If omitted, you still will be able to understand the current DB version and establish a connection.</p>
</li>
</ul>
<p>To handle the result of the <code>.open</code> you should subscribe to the following events:</p>
<ul>
<li><p><code>upgradeneeded</code>: happens when the provided DB version is higher than existing, and therefore DB update is required. The handler of the event is a version change transaction, which can create/delete objectStores and indexes;</p>
</li>
<li><p><code>success</code>: DB version change transaction is successful (if any) and the connection is established. In that case, you can access the DB through <code>request.result</code>;</p>
</li>
<li><p><code>error</code>: connection attempt / version change transaction throws an error;</p>
</li>
<li><p><code>blocked</code>: another code / tab maintains a connection to the DB with a smaller version number.</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> request = indexedDB.open(DB_NAME: <span class="hljs-built_in">string</span>, VERSION: <span class="hljs-built_in">number</span>);

<span class="hljs-comment">// Version change transaction</span>
request.onupgradeneeded = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// DB connection for the transaction operation</span>
  <span class="hljs-keyword">const</span> db: IDBDatabase = request.result;
  <span class="hljs-comment">// IDB transaction ref (version change transaction)</span>
  <span class="hljs-keyword">const</span> transaction: IDBTransaction = event.currentTarget.transaction;
};

request.onsuccess = <span class="hljs-function">() =&gt;</span> {
   <span class="hljs-comment">// establisehed connection to IDBDatabase  </span>
  <span class="hljs-keyword">const</span> db: IDBDatabase = request.result;
  db.version <span class="hljs-comment">// contains the VERSION of the DB</span>
};

request.onerror = <span class="hljs-function">(<span class="hljs-params">errorEvent</span>) =&gt;</span> {
  <span class="hljs-comment">// Error during updating the db  </span>
};

request.onblocked = <span class="hljs-function">() =&gt;</span> {
 <span class="hljs-comment">// DB is blocked </span>
 <span class="hljs-comment">// (another codepath / tab maintains connection to old DB version)</span>
};
</code></pre>
<p>Most of the real-life use cases open the database with version, as you want to ensure your code is up-to-date and has all keys, objectStores and indexes you need.</p>
<p><a target="_blank" href="https://codesandbox.io/p/sandbox/indexeddb-examples-5f4lms?file=%2Fsrc%2FdbConnection.ts%3A5%2C1">Async-await version of opening</a> the connection to the DB:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> db: IDBDatabase = <span class="hljs-keyword">await</span> openConnection(<span class="hljs-string">'myDatabaseName'</span>, <span class="hljs-number">4</span>, <span class="hljs-function">(<span class="hljs-params">db</span>) =&gt;</span> {
   <span class="hljs-comment">// version change transaction code goes here</span>
});

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">openConnection</span>(<span class="hljs-params">dbName: <span class="hljs-built_in">string</span>, version: <span class="hljs-built_in">number</span>, upgradeTxn: (db: IDBDatabase) =&gt; <span class="hljs-built_in">void</span></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">IDBDatabase</span>&gt; </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve,reject</span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> request = indexedDB.open(dbName, version);

        <span class="hljs-comment">// Version change transaction</span>
        request.onupgradeneeded = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">event</span>) </span>{
            upgradeTxn(request.result);
        }
        <span class="hljs-comment">// Update completed successfully, DB connection is established</span>
        request.onsuccess = <span class="hljs-function">() =&gt;</span> {
          resolve(request.result);
        };

        <span class="hljs-comment">// Something goes wrong during connection opening and / or </span>
        <span class="hljs-comment">// during version upgrade</span>
        request.onerror = <span class="hljs-function">(<span class="hljs-params">errorEvent</span>) =&gt;</span> {
          <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Cannot open db'</span>);
          reject(errorEvent);
        };

        <span class="hljs-comment">// Stands for blocked DB </span>
        request.onblocked = <span class="hljs-function">() =&gt;</span> {
          <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">'Db is blocked'</span>);
          reject(<span class="hljs-string">'db is blocked'</span>);
        };
    });
}
</code></pre>
<p>When the Database is created for the first time, we call <code>.open</code> with database name and (<em>usually</em>) with the <code>1</code> version of the DB. <em>Note</em>: you can use <em>any</em> number as a version of the database. As the DB doesn't exist yet, the new database will be created and your code will call a <code>version change transaction</code>.</p>
<p>📝 Version change transaction is a special type of transaction that can be accessed only when you create or upgrade the database version.</p>
<p>📝 Version change transaction has exclusive access to:</p>
<ul>
<li><p>objectStores manipulation: create / delete</p>
</li>
<li><p>keys definition: simple / compound / autoincremental</p>
</li>
<li><p>indexes manipulation: create / delete / etc.</p>
</li>
</ul>
<p>Once the transaction is completed, DB will have the new version set.</p>
<p>If there is an error during a transaction, the error event will be thrown, which can be caught using <code>request.onerror</code> handler.</p>
<p>If there is another connection to the same DB is maintained with smaller DB version number, the <code>.open</code> request will get <code>blocked</code> event thrown.</p>
<p>Overall schema of <code>indexedDB.open</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701633751557/9ae0ffc5-a155-4f7c-ab0d-b499dab09b4a.png" alt class="image--center mx-auto" /></p>
<p>Example of iDB creation with one objectStore and one additional index:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">openConnection</span>(<span class="hljs-params"></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">IDBDatabase</span>&gt; </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> request = indexedDB.open(<span class="hljs-string">"Understanding_IndexedDB"</span>, <span class="hljs-number">1</span>);

    <span class="hljs-comment">// Version change transaction</span>
    request.onupgradeneeded = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
      db = request.result; <span class="hljs-comment">// IDBDatabase</span>

      <span class="hljs-comment">// Only version change transaction has access to create/delete object stores</span>
      <span class="hljs-keyword">const</span> namesObjectStore = db.createObjectStore(<span class="hljs-string">"name-store"</span>, {
        keyPath: <span class="hljs-string">"id"</span>,
        <span class="hljs-comment">// We can create auto-increment primary key</span>
        autoIncrement: <span class="hljs-literal">true</span>,
      });
      <span class="hljs-comment">// Only version change transaction has access to create/delete indexes</span>
      namesObjectStore.createIndex(<span class="hljs-string">"index[name]"</span>, <span class="hljs-string">"name"</span>, { unique: <span class="hljs-literal">false</span> });
    };

    <span class="hljs-comment">// Will be called once onupgradeneeded is completed or </span>
    <span class="hljs-comment">// when the DB already has correct version</span>
    request.onsuccess = <span class="hljs-function">() =&gt;</span> {
      LOG<span class="hljs-string">`DB: <span class="hljs-subst">${TEST_DB_NAME}</span> created`</span>;
      resolve(request.result); <span class="hljs-comment">// return IDBDatabase</span>
    };
    <span class="hljs-comment">// Error in transaction</span>
    request.onerror = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
      LOG<span class="hljs-string">`DB: <span class="hljs-subst">${TEST_DB_NAME}</span> cannot be created: <span class="hljs-subst">${e}</span>`</span>;
      reject(e);
    };
    <span class="hljs-comment">// Current DB is blocked</span>
    request.onblocked = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
      LOG<span class="hljs-string">`DB: <span class="hljs-subst">${TEST_DB_NAME}</span> is blocked: <span class="hljs-subst">${e}</span>`</span>;
      reject(e);
    };
  });
}
</code></pre>
<p>📝: The versioning for <code>indexedDB.open</code> can only be non-decreasing. If the version passed to <code>indexedDB.open</code> is less than the real DB version, it would throw an error.</p>
<p>It's extremely important!</p>
<p>📝 if you accidentally push a version, which breaks client processing, you cannot roll back changes. You have to do the fix forward. IndexedDB doesn't have "version rollback" transactions.</p>
<h2 id="heading-how-to-create-objectstore">How to: create ObjectStore</h2>
<p>ObjectStore can be created / deleted inside version change transaction only.</p>
<p><code>createObjectStore</code> creates a new objectStore in your Database.</p>
<pre><code class="lang-typescript">idbDatabase.createObjectStore(
  OBJECT_STORE_NAME: <span class="hljs-built_in">string</span>, 
  KEY: {
    keyPath: <span class="hljs-built_in">string</span> | <span class="hljs-built_in">Array</span>&lt;<span class="hljs-built_in">string</span>&gt;,
    autoIncrement: <span class="hljs-built_in">boolean</span> = <span class="hljs-literal">false</span> <span class="hljs-comment">// default</span>
  }
);
</code></pre>
<p>As values are flexible, the minimal variant of <code>createObjectStore</code> receives only the object store name:</p>
<pre><code class="lang-typescript">idbDatabase.createObjectStore(<span class="hljs-string">'myNewObjectStore'</span>);
</code></pre>
<p>In that case, objects in the store won't have the key presented and will use keys <em>separated from</em> the values. It is also called <code>out-of-line</code> keys.</p>
<p>The second argument of <code>createObjectStore</code> allows you to define the key for the object store, and it's declared by an object with 2 fields:</p>
<ul>
<li><p><code>keyPath</code>: string or array of strings, which represents the "primary key" for key-value storage.</p>
<ul>
<li>The key <em>has to</em> be unique. e.g. if you have <code>keyPath</code> defined for a person's <code>name</code>, you are about to receive an error as soon as you try to persist 2 persons with the same name.</li>
</ul>
</li>
<li><p><code>autoIncrement</code>: a flag that shows if the key is auto-incremental or not. The default is <code>false</code>.</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> namesObjectStore = db.createObjectStore(<span class="hljs-string">"name-store"</span>, {
  keyPath: <span class="hljs-string">"id"</span>,  
  autoIncrement: <span class="hljs-literal">true</span>,
});
</code></pre>
<p>You can use multiple fields as a keyPath:</p>
<pre><code class="lang-typescript">db.createObjectStore(<span class="hljs-string">"address-store"</span>, {
  <span class="hljs-comment">// Complex keypath based on 6 keys:</span>
  <span class="hljs-comment">// <span class="hljs-doctag">NOTE:</span> don't do keypath based on address, use it as index for production code ;)</span>
  keyPath: [<span class="hljs-string">"country"</span>, <span class="hljs-string">"city"</span>, <span class="hljs-string">"street"</span>, <span class="hljs-string">"house"</span>, <span class="hljs-string">"flat"</span>, <span class="hljs-string">"zip"</span>],
});
</code></pre>
<p>📝 The order of fields in KeyPath is important. That's exactly how the data will be presented in your DB.</p>
<p>To delete an object store, call <code>deleteObjectStore</code> :</p>
<pre><code class="lang-typescript">idbDatabase.deleteObjectStore(<span class="hljs-string">'myNewObjectStore'</span>);
</code></pre>
<h2 id="heading-how-to-create-an-index">How to: create an index</h2>
<p>📝 Index is an additional way of accessing values in the object store. Technically index references the key, which refers to the value in the object store.</p>
<p>📝 Indexes creation / deletion can happen inside Version change transaction only.</p>
<p>To create an index you should have a reference to the object store:</p>
<pre><code class="lang-typescript">objectStore.createIndex(
  UNIQUE_INDEX_NAME: <span class="hljs-built_in">string</span>, 
  KEY_PATH: <span class="hljs-built_in">string</span> | <span class="hljs-built_in">Array</span>&lt;<span class="hljs-built_in">string</span>&gt;, 
  OPTIONS: {
    <span class="hljs-comment">// Shows if duplicate records for this index are allowed</span>
    unique: <span class="hljs-built_in">boolean</span> = <span class="hljs-literal">false</span>, 
    <span class="hljs-comment">// Index can resolve entries inside array:</span>
    multiEntry: <span class="hljs-built_in">boolean</span> = <span class="hljs-literal">false</span>,
  }
);
</code></pre>
<p>The following code creates an index based on the <code>name</code> field. The index is marked as non-unique, and it might be omitted, as <code>unique: false</code> is the default value:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Create an object store with out-of-line key:</span>
<span class="hljs-keyword">const</span> namesObjectStore = db.createObjectStore(<span class="hljs-string">"name-store"</span>);
<span class="hljs-comment">// Add index:</span>
namesObjectStore.createIndex(<span class="hljs-string">"index[name]"</span>, <span class="hljs-string">"name"</span>, { unique: <span class="hljs-literal">false</span> });
</code></pre>
<p>To create an index based on several fields we use <code>Array&lt;string&gt;</code> as a keyPath:</p>
<pre><code class="lang-typescript">event.currentTarget.transaction
  .objectStore(<span class="hljs-string">"name-store"</span>)
  .createIndex(<span class="hljs-string">"index[name + lastName]"</span>, [<span class="hljs-string">"name"</span>, <span class="hljs-string">"lastName"</span>], {
    unique: <span class="hljs-literal">false</span>,
  });
</code></pre>
<p>📝 The order of fields in KeyPath is important. That's exactly how the data will be indexed in your DB.</p>
<p>📝 sometimes, you may want to store an array with strings or numbers. And you might want to index the data based on this array. To achieve this, you can use an index with <code>multiEntry</code> option:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Call inside onupgradeneeded callback:</span>
event.currentTarget.transaction
  <span class="hljs-comment">// select object store:</span>
  .objectStore(<span class="hljs-string">"name-store"</span>)
  <span class="hljs-comment">// citizenship field is Array&lt;String&gt;, like: ['USA', 'UK', ...]</span>
  .createIndex(<span class="hljs-string">"index[citizenship]"</span>, <span class="hljs-string">"citizenship"</span>, {
    multiEntry: <span class="hljs-literal">true</span>,
  });
</code></pre>
<p><a target="_blank" href="https://codesandbox.io/p/sandbox/indexeddb-examples-5f4lms?file=%2Fsrc%2F3.fillDb.ts">Example of records:</a></p>
<pre><code class="lang-typescript">nameStore.add({ name: <span class="hljs-string">"John"</span>, lastName: <span class="hljs-string">"Doe"</span>, citizenship: [<span class="hljs-string">"USA"</span>] });
nameStore.add({
  name: <span class="hljs-string">"Alice"</span>,
  lastName: <span class="hljs-string">"Smith"</span>,
  citizenship: [<span class="hljs-string">"USA"</span>, <span class="hljs-string">"UK"</span>],
});
nameStore.add({
  name: <span class="hljs-string">"Bob"</span>,
  lastName: <span class="hljs-string">"Johnson"</span>,
  citizenship: [<span class="hljs-string">"USA"</span>, <span class="hljs-string">"Germany"</span>],
});
nameStore.add({
  name: <span class="hljs-string">"Eva"</span>,
  lastName: <span class="hljs-string">"Anderson"</span>,
  citizenship: [<span class="hljs-string">"Greece"</span>],
});
</code></pre>
<p>If we request data based on <code>index[citizenship]</code> <a target="_blank" href="https://codesandbox.io/p/sandbox/indexeddb-examples-5f4lms?file=%2Fsrc%2F4.multiEntryIndex.ts">field with <code>USA</code> as a request</a>, we will receive <code>John</code>, <code>Alice</code>, and <code>Bob</code>, but not <code>Eva</code>.</p>
<h2 id="heading-how-to-idbtransaction">How to: IDBTransaction</h2>
<p>There are 3 types of IDBTransactions:</p>
<ol>
<li><p>📝 <code>readonly</code>: only read requests. It's possible to have multiple readonly transactions opened to the same objectStore in the same time;</p>
</li>
<li><p>📝 <code>readwrite</code>: requires write access to the object store. You can have only 1 active transaction per time for the particular objectStore</p>
</li>
<li><p>📝 Version change: happens exclusively when the connection to the DB gets opened and the requested version is higher than the current DB version. This type of transaction has access to objectStores, indexes creation / deletion.</p>
</li>
</ol>
<p>To create a transaction, you should have an active connection to the Database:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> transaction: IDBTransaction = (db: IDBDatabase)
  .transaction(<span class="hljs-built_in">Array</span>&lt;ObjectStoreName&gt;, <span class="hljs-string">"readwrite"</span> | <span class="hljs-string">"readonly"</span>);
</code></pre>
<p>📝 Transaction gives you access to ObjectStore (depending on transaction permissions to read / write operations):</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Open DB connection (if we didn't do it before)</span>
<span class="hljs-keyword">const</span> db: IDBDatabase = <span class="hljs-keyword">await</span> openConnection(<span class="hljs-string">"myTestDb"</span>, <span class="hljs-number">4</span>);

<span class="hljs-comment">// Open readwrite transaction</span>
<span class="hljs-keyword">const</span> txn = db.transaction([<span class="hljs-string">"name-store"</span>, <span class="hljs-string">"address-store"</span>], <span class="hljs-string">"readwrite"</span>);

<span class="hljs-comment">// Request name-store objectStore</span>
<span class="hljs-keyword">const</span> nameStore: IDBObjectStore = txn.objectStore(<span class="hljs-string">"name-store"</span>);
</code></pre>
<p>📝 Transaction is the only way to get/set the data:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> idbRequest: IDBRequest = nameStore.add({
  name: <span class="hljs-string">"Alice"</span>,
  lastName: <span class="hljs-string">"Smith"</span>,
  citizenship: [<span class="hljs-string">"USA"</span>, <span class="hljs-string">"UK"</span>],
});
</code></pre>
<p>📝 You have direct control on when to abort (rollback) the transaction or when to commit it.</p>
<p>📝 <code>txn.commit</code> ends current transaction and <a target="_blank" href="https://codesandbox.io/p/sandbox/indexeddb-examples-5f4lms?file=%2Fsrc%2F5.commitTransaction.ts">persists the data</a> from the IDBRequests you had:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Open DB connection (if we didn't do it before)</span>
<span class="hljs-keyword">const</span> db: IDBDatabase = <span class="hljs-keyword">await</span> openConnection(<span class="hljs-string">"myTestDb"</span>, <span class="hljs-number">4</span>);
<span class="hljs-comment">// ...</span>
<span class="hljs-comment">// Open readwrite transaction</span>
<span class="hljs-keyword">const</span> txn = db.transaction([<span class="hljs-string">"name-store"</span>, <span class="hljs-string">"address-store"</span>], <span class="hljs-string">"readwrite"</span>);

<span class="hljs-comment">// Request name-store objectStore</span>
<span class="hljs-keyword">const</span> nameStore: IDBObjectStore = txn.objectStore(<span class="hljs-string">"name-store"</span>);

<span class="hljs-keyword">const</span> idbRequest: IDBRequest = nameStore.add({
  name: <span class="hljs-string">"Alice"</span>,
  lastName: <span class="hljs-string">"Smith"</span>,
  citizenship: [<span class="hljs-string">"USA"</span>, <span class="hljs-string">"UK"</span>],
});

<span class="hljs-comment">// nameStore got new record Alice Smith</span>
idbRequest.onsuccess = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> { 
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"idb request success"</span>);
  txn.commit(); <span class="hljs-comment">// Commits the current transaction</span>
}

txn.oncomplete = (e) = {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"idb transaction is done"</span>);
}

txn.onabort = (e) = {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"idb transaction is aborted"</span>);
}

<span class="hljs-comment">// Alice Smith is written to the db</span>
<span class="hljs-comment">// output:</span>
<span class="hljs-comment">// idb request success</span>
<span class="hljs-comment">// idb transaction is done</span>
</code></pre>
<p>📝 <code>txn.abort()</code> closes the transaction and <a target="_blank" href="https://codesandbox.io/p/sandbox/indexeddb-examples-5f4lms?file=%2Fsrc%2F6.abortTransaction.ts">rolls back all the changes</a>:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Open DB connection (if we didn't do it before)</span>
<span class="hljs-keyword">const</span> db: IDBDatabase = <span class="hljs-keyword">await</span> openConnection(<span class="hljs-string">"myTestDb"</span>, <span class="hljs-number">4</span>);
<span class="hljs-comment">// ...</span>
<span class="hljs-comment">// Open readwrite transaction</span>
<span class="hljs-keyword">const</span> txn = db.transaction([<span class="hljs-string">"name-store"</span>, <span class="hljs-string">"address-store"</span>], <span class="hljs-string">"readwrite"</span>);

<span class="hljs-comment">// Request name-store objectStore</span>
<span class="hljs-keyword">const</span> nameStore: IDBObjectStore = txn.objectStore(<span class="hljs-string">"name-store"</span>);

<span class="hljs-keyword">const</span> idbRequest: IDBRequest = nameStore.add({
  name: <span class="hljs-string">"Alice"</span>,
  lastName: <span class="hljs-string">"Smith"</span>,
  citizenship: [<span class="hljs-string">"USA"</span>, <span class="hljs-string">"UK"</span>],
});

<span class="hljs-comment">// nameStore got new record Alice Smith</span>
idbRequest.onsuccess = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> { 
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"idb request success"</span>);
  txn.commit(); <span class="hljs-comment">// Commits the current transaction</span>
}

txn.oncomplete = (e) = {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"idb transaction is done"</span>);
}

txn.onabort = (e) = {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"idb transaction is aborted"</span>);
}

<span class="hljs-comment">// Alice Smith is NOT written to the db (rolled back)</span>
<span class="hljs-comment">// output:</span>
<span class="hljs-comment">// idb request success</span>
<span class="hljs-comment">// idb transaction is aborted</span>
</code></pre>
<h3 id="heading-transaction-concurrency">Transaction concurrency:</h3>
<p>📝 Transactions are queued for the execution once <code>indexedDB.transaction</code> is requested.</p>
<p>📝 <code>readwrite</code> transactions for the same objectStore are <a target="_blank" href="https://codesandbox.io/p/sandbox/indexeddb-examples-5f4lms?file=%2Fsrc%2F7.concurrentWrites.ts">executed consequently</a> with the the order defined by <code>.transaction</code> requests:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> db: IDBDatabase = <span class="hljs-keyword">await</span> openConnection(<span class="hljs-string">"myTestDb"</span>, <span class="hljs-number">4</span>);
<span class="hljs-comment">// ...</span>
<span class="hljs-comment">// 1st txn</span>
<span class="hljs-keyword">const</span> txn = db.transaction([<span class="hljs-string">"name-store"</span>, <span class="hljs-string">"address-store"</span>], <span class="hljs-string">"readwrite"</span>);

<span class="hljs-comment">// 2nd txn</span>
<span class="hljs-keyword">const</span> txn2 = db.transaction([<span class="hljs-string">"name-store"</span>], <span class="hljs-string">"readwrite"</span>);

<span class="hljs-keyword">const</span> nameStore2: IDBObjectStore = txn2.objectStore(<span class="hljs-string">"name-store"</span>);

<span class="hljs-keyword">const</span> idbRequest2: IDBRequest = nameStore2.add({name: <span class="hljs-string">"Alice 2"</span>});

<span class="hljs-keyword">const</span> nameStore: IDBObjectStore = txn.objectStore(<span class="hljs-string">"name-store"</span>);

<span class="hljs-keyword">const</span> idbRequest: IDBRequest = nameStore.add({name: <span class="hljs-string">"Alice"</span>});

<span class="hljs-comment">// Alice will be written first</span>
<span class="hljs-comment">// Alice2 will be written after the first transaction gets completed</span>
</code></pre>
<p>📝 <code>readonly</code> transaction can be <a target="_blank" href="https://codesandbox.io/p/sandbox/indexeddb-examples-5f4lms?file=%2Fsrc%2F8.concurrentReads.ts">executed concurrently</a> and the order is not determined in that case:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// 1st txn</span>
<span class="hljs-keyword">const</span> txn = db.transaction([<span class="hljs-string">"name-store"</span>, <span class="hljs-string">"address-store"</span>], <span class="hljs-string">"readonly"</span>);

<span class="hljs-comment">// 2nd txn</span>
<span class="hljs-keyword">const</span> txn2 = db.transaction([<span class="hljs-string">"name-store"</span>, <span class="hljs-string">"address-store"</span>], <span class="hljs-string">"readonly"</span>);

<span class="hljs-keyword">const</span> nameStore2: IDBObjectStore = txn2.objectStore(<span class="hljs-string">"name-store"</span>);
<span class="hljs-keyword">const</span> nameStore: IDBObjectStore = txn.objectStore(<span class="hljs-string">"name-store"</span>);

<span class="hljs-keyword">const</span> request2 = nameStore2.getAll();

<span class="hljs-keyword">const</span> request = nameStore.getAll();

txn2.oncomplete = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
  LOG<span class="hljs-string">`The 2nd transaction is done`</span>;
  LOG<span class="hljs-string">`<span class="hljs-subst">${request2.result}</span>`</span>;
  LOG<span class="hljs-string">`First request state: <span class="hljs-subst">${request.readyState}</span>`</span>;
};

txn2.onabort = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
  LOG<span class="hljs-string">`idb transaction is aborted`</span>;
};

txn.oncomplete = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
  LOG<span class="hljs-string">`The 1st transaction is done`</span>;
  LOG<span class="hljs-string">`<span class="hljs-subst">${request.result}</span>`</span>;
  LOG<span class="hljs-string">`Second request state: <span class="hljs-subst">${request2.readyState}</span>`</span>;
};

txn.onabort = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
  LOG<span class="hljs-string">`idb transaction is aborted`</span>;
};
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1702240095279/c206d80b-d61d-4f7b-9d03-b2062acc963f.png" alt class="image--center mx-auto" /></p>
<p>📝 If you have 2 transactions: readonly and readwrite, they will be <a target="_blank" href="https://codesandbox.io/p/sandbox/indexeddb-examples-5f4lms?file=%2Fsrc%2F9.concurrentReadWrite.ts">executed in order</a> of <code>.transaction</code> .</p>
<p>📝 Several readwrite transactions for the different <a target="_blank" href="https://codesandbox.io/p/sandbox/indexeddb-examples-5f4lms?file=%2Fsrc%2F10.concurrentWriteDifferentObjectStores.ts">objectStores are executed concurrently</a>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> txn = db.transaction([<span class="hljs-string">"address-store"</span>], <span class="hljs-string">"readwrite"</span>);
<span class="hljs-keyword">const</span> txn2 = db.transaction([<span class="hljs-string">"name-store"</span>], <span class="hljs-string">"readwrite"</span>);

<span class="hljs-keyword">const</span> addressStore: IDBObjectStore = txn.objectStore(<span class="hljs-string">"address-store"</span>);
<span class="hljs-keyword">const</span> nameStore2: IDBObjectStore = txn2.objectStore(<span class="hljs-string">"name-store"</span>);

nameStore2.add({name: <span class="hljs-string">"Alice 2"</span>});

<span class="hljs-keyword">const</span> clearRequest = addressStore.clear();
clearRequest.onsuccess = <span class="hljs-function">() =&gt;</span> {
  addAddresses(addressStore); <span class="hljs-comment">// adds 10+ records to the db</span>
};

txn2.oncomplete = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
  LOG<span class="hljs-string">`name-store, 2nd transaction is done`</span>;
};

txn.oncomplete = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
  LOG<span class="hljs-string">`address-store, 1st transaction is done`</span>;
};

<span class="hljs-comment">// txn2 will be completed sooner, than txn in most of the cases, </span>
<span class="hljs-comment">// despite we start txn first. However the order is not strict here.</span>
<span class="hljs-comment">// (just becasue, txn2 has too many things to do)</span>
</code></pre>
<p>📝 Rules above are scaled the same way if multiple tabs are opened for the same domain (only one readwrite transaction to a single objectStore, and etc).</p>
<h3 id="heading-transaction-lifetime">Transaction lifetime:</h3>
<p>📝 Transaction is maintained by browser until one of these events:</p>
<ul>
<li><p><code>transaction.commit()</code> is called. It will end transaction and commit all the changes. Any pending <code>IDBRequest</code> will be completed with <code>success</code>, <code>IDBTransaction</code> will be completed with <code>complete</code> event</p>
</li>
<li><p>No any pending <code>IDBRequest</code> / <code>success</code> callbacks in the next <strong>macro task</strong> call of the browser event loop.</p>
<ul>
<li><p>In that case, as no other work is planned, transaction is treated as successfully completed and IndexedDB behaviour is the same, as per <code>.commit()</code> call.</p>
</li>
<li><p>IMPORTANT: transaction still <a target="_blank" href="https://codesandbox.io/p/sandbox/indexeddb-examples-5f4lms?file=%2Fsrc%2F12.keepTransactionAliveWithPromiseLoop.ts">will be maintained in case of microtasks</a>.</p>
</li>
</ul>
</li>
<li><p><code>transaction.abort()</code> is called. In that case all the changes will be rolled back. <code>IDBRequest</code> ends with <code>abort</code> event which bubbles to <code>IDBTransaction</code></p>
</li>
<li><p>Runtime Errors which prevent transaction to be committed (changes won't be saved):</p>
<ul>
<li><p>Uncaught exception in the <code>IDBRequest</code> <code>success</code>/<code>error</code> handler.</p>
</li>
<li><p>I/O error:</p>
<ul>
<li><p>Fail to write on disk;</p>
</li>
<li><p>Quota exceeded;</p>
</li>
<li><p>OS failure;</p>
</li>
<li><p>Hardware failure;</p>
</li>
</ul>
</li>
<li><p>Browser crash / etc;</p>
</li>
<li><p>Fail to write data due to constrains (e.g. unique index / key);</p>
</li>
</ul>
</li>
</ul>
<p>📝 If for some reason, you want to keep transaction active for some period of time, <a target="_blank" href="https://codesandbox.io/p/sandbox/indexeddb-examples-5f4lms?file=%2Fsrc%2F11.keepTransactionAliveNonBlocking.ts">you should keep creating empty</a> <code>IDBRequest</code>, to ensure next event loop ticks have pending <code>IDBRequest</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> txn = db.transaction([<span class="hljs-string">"name-store"</span>], <span class="hljs-string">"readwrite"</span>);
LOG<span class="hljs-string">`Try to click other commands, like fillDb! This transaction keeps readwrite lock`</span>;

<span class="hljs-keyword">const</span> nameStore: IDBObjectStore = txn.objectStore(<span class="hljs-string">"name-store"</span>);

<span class="hljs-keyword">let</span> endTransaction = <span class="hljs-literal">false</span>;
<span class="hljs-keyword">const</span> startTime = <span class="hljs-built_in">Date</span>.now();
<span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> (endTransaction = <span class="hljs-literal">true</span>), <span class="hljs-number">5000</span>);
<span class="hljs-keyword">let</span> idbRequestCycles = <span class="hljs-number">0</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">cycleIdbRequest</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">if</span> (endTransaction) {
    txn.commit();
    <span class="hljs-keyword">return</span>;
  }
  idbRequestCycles++;
  <span class="hljs-comment">// request non-existing item</span>
  <span class="hljs-keyword">const</span> idbRequest = nameStore.get(<span class="hljs-literal">Infinity</span>);
  idbRequest.onsuccess = cycleIdbRequest;
}

cycleIdbRequest();
txn.oncomplete = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
  LOG<span class="hljs-string">`Transaction is completed after <span class="hljs-subst">${
    (<span class="hljs-built_in">Date</span>.now() - startTime) / <span class="hljs-number">1000</span>
  }</span> sec. In total <span class="hljs-subst">${idbRequestCycles}</span> IDBRequests were created`</span>;
};
</code></pre>
<p>Such a code creates lots of empty, chained <code>IDBRequest</code> queries and keeps transaction alive, while the browser is not entirely blocked (however, it consumes a lot of CPU). Example of output:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1702243904971/3cb9cb97-e3cc-447f-98d0-bcdb7d950012.png" alt class="image--center mx-auto" /></p>
<p>📝: It "might" look sometimes a good idea to "keep" transaction alive in that way, when something is calculated in a background, but keep in mind:</p>
<ol>
<li><p>It consumes a lot of CPU</p>
</li>
<li><p>Your transaction will be executed slower, if you keep lots of IDBRequests inside the same IDBTransaction (in other words, for long-running IDB transactions, you can see performance degradation).</p>
</li>
</ol>
<p>📝 If you have transaction alive, and keep spawning micro tasks with no any pending <code>IDBRequest</code>, the transaction will remain alive (<a target="_blank" href="https://codesandbox.io/p/sandbox/indexeddb-examples-5f4lms?file=%2Fsrc%2F12.keepTransactionAliveWithPromiseLoop.ts">however the browser's thread will be blocked too</a>):</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> txn = db.transaction([<span class="hljs-string">"name-store"</span>], <span class="hljs-string">"readwrite"</span>);
<span class="hljs-keyword">const</span> startTime = <span class="hljs-built_in">Date</span>.now();
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">cyclePromiseLoop</span>(<span class="hljs-params"></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">void</span>&gt; </span>{
  <span class="hljs-keyword">if</span> (<span class="hljs-built_in">Date</span>.now() - startTime &gt; <span class="hljs-number">2000</span>) {
    txn.objectStore(<span class="hljs-string">"name-store"</span>).count().onsuccess = <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {       
      <span class="hljs-keyword">const</span> result = event.target.result;
      LOG<span class="hljs-string">`IDBRequest ended after <span class="hljs-subst">${
        (<span class="hljs-built_in">Date</span>.now() - startTime) / <span class="hljs-number">1000</span>
      }</span> sec. Result: <span class="hljs-subst">${result}</span>`</span>;
    };
    txn.commit();
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.resolve();
  }
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.resolve().then(cyclePromiseLoop);
}

<span class="hljs-keyword">await</span> cyclePromiseLoop();
txn.oncomplete = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
  LOG<span class="hljs-string">`Transaction is completed after <span class="hljs-subst">${(<span class="hljs-built_in">Date</span>.now() - startTime) / <span class="hljs-number">1000</span>}</span> sec.`</span>;
};
</code></pre>
<p><code>IDBRequest</code> will be <a target="_blank" href="https://github.com/xnimorz/indexedDB-examples/blob/main/src/12.keepTransactionAliveWithPromiseLoop.ts">executed successfully</a>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1702337579408/ab803ba7-a69d-4fd8-98f2-c036eb4dde80.png" alt class="image--center mx-auto" /></p>
<p>📝 If the next macrotask starts with no pending <code>IDBRequest</code> or <code>idbRequest.onsuccess</code> handlers, the transaction will be completed.</p>
<p>Simple way to verify is to put <code>IDBRequest</code> inside <code>setTimeout</code> (macrotask). You can see that the transaction will be closed before you start <code>IDBRequest</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> txn = db.transaction([<span class="hljs-string">"name-store"</span>], <span class="hljs-string">"readonly"</span>);

<span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> nameStore = txn.objectStore(<span class="hljs-string">"name-store"</span>);
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> read = nameStore.count();
    read.onerror = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
      LOG<span class="hljs-string">`Error performing request: <span class="hljs-subst">${e}</span>`</span>;
    };
    read.onsuccess = <span class="hljs-function">() =&gt;</span> {
      LOG<span class="hljs-string">`result: <span class="hljs-subst">${read.result}</span>`</span>;
    };
  } <span class="hljs-keyword">catch</span> (e) {
    <span class="hljs-comment">// This code will be executed (as transaction is not active)</span>
    LOG<span class="hljs-string">`Runtime Error performing request: <span class="hljs-subst">${e}</span>`</span>;
  }
}, <span class="hljs-number">0</span>);

txn.oncomplete = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
  LOG<span class="hljs-string">`Transaction is completed`</span>;
};

txn.onabort = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
  LOG<span class="hljs-string">`idb transaction is aborted`</span>;
};
</code></pre>
<p><a target="_blank" href="https://codesandbox.io/p/sandbox/indexeddb-examples-5f4lms?file=%2Fsrc%2F13.performRequestInSetTimeout.ts">That code results in constant error</a>, because the transaction is not active:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1702244722585/0ed8a90f-8d0c-4bed-96ce-2d4756b98dee.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-transaction-durability">Transaction durability:</h3>
<p>📝 There are 2 types of transaction durability:</p>
<ul>
<li><p><code>strict</code>: transaction will be considered <code>commited</code> if and only if the data is persisted on disk</p>
</li>
<li><p><code>relaxed</code>: browser marks transaction as soon as changes get written to the operating system. "Physically" the data might be written later.</p>
</li>
</ul>
<p>In manual tests the difference between <code>strict</code> and <code>relaxed</code> durability might be huge (up to tens milliseconds per transaction). <a target="_blank" href="https://codesandbox.io/p/sandbox/indexeddb-examples-5f4lms?file=%2Fsrc%2F14.durabilityCompare.ts">Run "Relaxed vs strict durability" test</a> to see example:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">txn</span>(<span class="hljs-params">durability: "strict" | "relaxed"</span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">void</span>&gt; </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> txn = idb.transaction([<span class="hljs-string">"name-store"</span>], <span class="hljs-string">"readwrite"</span>, {
      durability,
    });

    <span class="hljs-keyword">const</span> nameStore = txn.objectStore(<span class="hljs-string">"name-store"</span>);
    nameStore.add({
      name: <span class="hljs-string">`Name #<span class="hljs-subst">${<span class="hljs-built_in">Math</span>.trunc(<span class="hljs-built_in">Math</span>.random() * <span class="hljs-number">1000</span>)}</span>`</span>,
    });
    txn.oncomplete = <span class="hljs-function">() =&gt;</span> {
      resolve();
    };
  });
}

LOG<span class="hljs-string">`Strict durability, 100 runs`</span>;
<span class="hljs-keyword">let</span> startTime = performance.now();
<span class="hljs-keyword">await</span> <span class="hljs-built_in">Promise</span>.all(<span class="hljs-built_in">Array</span>.from({ length: <span class="hljs-number">100</span> }).map(<span class="hljs-function">() =&gt;</span> txn(<span class="hljs-string">"strict"</span>)));
LOG<span class="hljs-string">`Strict: <span class="hljs-subst">${performance.now() - startTime}</span> ms`</span>;
LOG<span class="hljs-string">`Relaxed durability, 100 runs`</span>;
startTime = performance.now();
<span class="hljs-keyword">await</span> <span class="hljs-built_in">Promise</span>.all(<span class="hljs-built_in">Array</span>.from({ length: <span class="hljs-number">100</span> }).map(<span class="hljs-function">() =&gt;</span> txn(<span class="hljs-string">"relaxed"</span>)));
LOG<span class="hljs-string">`Relaxed: <span class="hljs-subst">${performance.now() - startTime}</span> ms`</span>;
</code></pre>
<p>For 100 runs the difference would be huge:</p>
<pre><code class="lang-typescript">Strict durability, <span class="hljs-number">100</span> runs
Strict: <span class="hljs-number">2278</span> ms <span class="hljs-comment">// ~ 22ms per transaction</span>
Relaxed durability, <span class="hljs-number">100</span> runs
Relaxed: <span class="hljs-number">39</span> ms <span class="hljs-comment">// ~0.39ms per transaction</span>
</code></pre>
<p>📝 Transaction durability can be defined when you request a transaction:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> txn = db.transaction([<span class="hljs-string">"name-store"</span>], <span class="hljs-string">"readwrite"</span>, {
  durability: <span class="hljs-string">"relaxed"</span>, <span class="hljs-comment">// or "strict"</span>
});
</code></pre>
<p>📝 Firefox and Chrome process transactions differently. Firefox uses <code>relaxed</code> durability by default, while Chrome is using <code>strict</code>. Generally, <code>relaxed</code> durability is reliable enough (unless OS / hardware issues).</p>
<h2 id="heading-how-to-idbobjectstore">How to: IDBObjectStore</h2>
<p>The entity that is "analog" for tables in Databases is ObjectStore.</p>
<p>ObjectStore contains all the methods and incapsulates all the processing for the data.</p>
<p>📝 ObjectStore is accessed from the transaction that requested the lock for that particular ObjectStore:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> txn = db.transaction([<span class="hljs-string">"name-store"</span>], <span class="hljs-string">"readwrite"</span>);
<span class="hljs-keyword">const</span> nameStore: IDBObjectStore = txn.objectStore(<span class="hljs-string">"name-store"</span>);
</code></pre>
<p>You cannot request an access to ObjectStore which you don't declare in the transaction:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> txn = db.transaction([<span class="hljs-string">"name-store"</span>], <span class="hljs-string">"readwrite"</span>);
<span class="hljs-keyword">const</span> addressStore = txn.objectStore(<span class="hljs-string">"address-store"</span>); <span class="hljs-comment">// ERROR!</span>
</code></pre>
<p>Object store consist in:</p>
<ul>
<li><p>Objects, which are presented by keys and values. Usually key is a part of value, but sometimes you can use out-of-line, which are not stored in value.</p>
</li>
<li><p>Key: presented by either single field / several fields or out-of-line key</p>
</li>
<li><p>Indexes: an additional way of accessing values in your object store. Technically index references the key, which refers to the value in object store.</p>
</li>
</ul>
<p>IDBObjectStore provides all the API to:</p>
<ul>
<li><p>Request data</p>
</li>
<li><p>Write / re-write data</p>
</li>
<li><p>Iterate through data: cursors, indexes. You also can mutate / delete data while iterating.</p>
</li>
</ul>
<p>📝 IDBObjectStore operations are ordered. If you <a target="_blank" href="https://codesandbox.io/p/sandbox/indexeddb-examples-5f4lms?file=%2Fsrc%2F15.orderExample.ts">subsequentely call methods</a>, they are guaranteed to be executed in the order they were planned:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> txn = idb.transaction([<span class="hljs-string">"name-store"</span>], <span class="hljs-string">"readwrite"</span>);

<span class="hljs-keyword">const</span> nameStore = txn.objectStore(<span class="hljs-string">"name-store"</span>);
<span class="hljs-comment">// We call method sequentially, </span>
<span class="hljs-comment">// but IDBRequest are planned without</span>
<span class="hljs-comment">// awaiting the end of previous one:</span>
nameStore.clear();
nameStore.add({
  name: <span class="hljs-string">`Mark`</span>,
  lastName: <span class="hljs-string">`Smith`</span>,
});
nameStore.count().onsuccess = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
  <span class="hljs-comment">// You can find that TS typing is not the best for IndexedDB</span>
  <span class="hljs-comment">// @ts-expect-error </span>
  LOG<span class="hljs-string">`1st Count is: <span class="hljs-subst">${e.target.result}</span>`</span>;
};
nameStore.put({ name: <span class="hljs-string">"Alice"</span> });
nameStore.count().onsuccess = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
  <span class="hljs-comment">// @ts-expect-error</span>
  LOG<span class="hljs-string">`2st Count is: <span class="hljs-subst">${e.target.result}</span>`</span>;
};
nameStore.clear();
nameStore.count().onsuccess = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
  <span class="hljs-comment">// @ts-expect-error</span>
  LOG<span class="hljs-string">`3st Count is: <span class="hljs-subst">${e.target.result}</span>`</span>;
};

<span class="hljs-comment">// Output:</span>
<span class="hljs-comment">// 1st Count is: 1</span>
<span class="hljs-comment">// 2st Count is: 2</span>
<span class="hljs-comment">// 3st Count is: 0</span>
</code></pre>
<h3 id="heading-idbobjectstore-methods-from-readonly-readwrite-transactions">IDBObjectStore methods from readonly / readwrite transactions:</h3>
<ul>
<li><p><code>.add(VALUE, ?KEY)</code> stores the provided VALUE in the ObjectStore. Returns an <code>IDBRequest</code> . Works only within <code>readwrite</code> transaction</p>
<ul>
<li>📝 Browser is using structured clone (deep clone) to make a copy of the value to persist.</li>
</ul>
</li>
<li><p><code>.put(item, ?key)</code>: updates an object by provided key, or adds a new object if there is no object in the provided key. Similar to <code>.add</code> but able to update the data stored, not only to add items. Works in <code>readwrite</code> transaction;</p>
</li>
<li><p><code>.clear()</code>: removes all the data from the ObjectStore. Returns an <code>IDBRequest</code> . Works only inside <code>readwrite</code> transaction;</p>
</li>
<li><p><code>.count(key | KeyRange)</code>: returns the <code>IDBRequest</code>, which would store in result the number of elements, matched to provided key value or KeyRange. If no arguments is provided, it will returns the overall amount of items in ObjectStore;</p>
</li>
<li><p><code>.delete(key | KeyRange)</code>: removes the data matched to <code>key</code> or within <code>KeyRange</code> . Only for <code>readwrite</code> transaction;</p>
</li>
<li><p><code>.get(key)</code>: fetches the data matched provided <code>key</code>. Returns an <code>IDBRequest</code>, which, once completed will have the data in <code>.result</code> field;</p>
</li>
<li><p><code>.getAll(?key | KeyRange, ?count)</code>: returns the <code>IDBRequest</code>, which would store in <code>.result</code> the array of objects, matched to provided key value or KeyRange. If no arguments is provided, it will returns all items in the ObjectStore. <code>count</code> is used to limit amount of data returned (Like LIMIT in SQL);</p>
</li>
<li><p><code>.getAllKeys(?Key | KeyRange, ?count)</code>: similar to <code>.getAll</code> but returns keys, instead of data;</p>
</li>
<li><p><code>.getKey(key | KeyRange)</code>: similar to <code>.get</code> but returns key, instead of data;</p>
</li>
<li><p><code>.index(name)</code>: opens an index (if exists, otherwise throws an error). returns <code>IDBIndex</code> which allows to request data from index.</p>
</li>
</ul>
<h3 id="heading-idbobjectstore-in-version-change-transaction">IDBObjectStore in version change transaction:</h3>
<p>When we upgrade IDBDatabase version, we have access to ObjectStores and hence we have 2 additional methods:</p>
<ul>
<li><p><code>.createIndex</code>: creates an index</p>
</li>
<li><p><code>.deleteIndex</code>: removes an index</p>
</li>
</ul>
<h3 id="heading-idbobjectstore-methods-which-work-with-idbcursor">IDBObjectStore methods which work with IDBCursor:</h3>
<p>Sometimes it's better to open a cursor and iterate through the data instead of fetching all data at once / trying to fetch data first and then update it in separate <code>IDBRequest</code>.</p>
<ol>
<li><p><code>.openCursor(key | KeyRange, direction)</code>: returns an <code>IDBRequest</code> which has <code>IDBCursorWithValue</code> in result. the cursor can iterate through data and manipulate it. The level of access from <code>IDBCursor</code> depends on the transaction type (readonly / readwrite). Possible directions:</p>
<ol>
<li><p><code>next</code>: increasing order of keys. Iterates through all records;</p>
</li>
<li><p><code>nextunique</code>: increasing order of keys. Ignores duplicates;</p>
</li>
<li><p><code>prev</code>: decreasing order. Goes through all records;</p>
</li>
<li><p><code>prevunique</code>: decreasing order, ignores duplicates;</p>
</li>
</ol>
</li>
<li><p><code>.openKeyCursor(key | KeyRange, direction)</code>: same but iterates only for keys;</p>
</li>
</ol>
<h2 id="heading-how-to-idbcursor-and-idbcursorwithvalue">How to: IDBCursor and IDBCursorWithValue</h2>
<p>📝 Cursors allows you to iterate through data record by record, allowing you to effectively mutate the data without multiple separated IDB requests.</p>
<p>📝 Cursor can be one of 2 types: <code>IDBCursor</code> or <code>IDBCursorWithValue</code>. <code>.openCursor</code> method always returns <code>IDBCursorWithValue</code>. <code>IDBCursorWithValue</code> has <code>.value</code> field which keeps the value at the cursor position.</p>
<p>Methods:</p>
<ul>
<li><p><code>.continue()</code>: moves cursor to the next position;</p>
</li>
<li><p><code>.advance(N)</code>: similar to <code>continue</code>, but moves the cursor for <code>N</code> positions;</p>
</li>
<li><p><code>.continuePrimaryKey(key, primaryKey)</code>: sets the cursor to the given index key and primary key given as arguments;</p>
</li>
<li><p><code>.delete()</code>: deletes the entry at the cursor position. Returns <code>IDBRequest</code></p>
</li>
<li><p><code>.update(value)</code>: updates the value at the cursor position. Returns <code>IDBRequest</code></p>
</li>
</ul>
<p><a target="_blank" href="https://codesandbox.io/p/sandbox/indexeddb-examples-5f4lms?file=%2Fsrc%2F16.cursorExample.ts">Cursor processing example</a>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> txn = idb.transaction([<span class="hljs-string">"name-store"</span>], <span class="hljs-string">"readwrite"</span>);
<span class="hljs-keyword">const</span> nameStore = txn.objectStore(<span class="hljs-string">"name-store"</span>);

<span class="hljs-keyword">let</span> mutatedEntries = <span class="hljs-number">0</span>;
<span class="hljs-keyword">let</span> deletedEntries = <span class="hljs-number">0</span>;
<span class="hljs-keyword">let</span> total = <span class="hljs-number">0</span>;
<span class="hljs-comment">// Iterate through all the entries</span>
<span class="hljs-keyword">const</span> request = nameStore.openCursor();
request.onsuccess = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> cursor = request.result;
  <span class="hljs-keyword">if</span> (cursor) {
    total++;
    <span class="hljs-keyword">if</span> (cursor.value.name.startsWith(<span class="hljs-string">"N"</span>)) {
      deletedEntries++;
      cursor.delete();
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">if</span> (!cursor.value.updated) {
        <span class="hljs-keyword">const</span> newValue = { ...cursor.value, updated: <span class="hljs-literal">true</span> };
        mutatedEntries++;
        cursor.update(newValue);
      }
    }
    cursor.continue();
  }
};

txn.oncomplete = <span class="hljs-function">() =&gt;</span> {
  LOG<span class="hljs-string">`Transaction is completed. Deleted: <span class="hljs-subst">${deletedEntries}</span>, mutated: <span class="hljs-subst">${mutatedEntries}</span>, total: <span class="hljs-subst">${total}</span>`</span>;
};
</code></pre>
<p>You don't have an access to <code>update</code> / <code>delete</code> methods if you are in <code>readonly</code> transaction, however, you still have an access to opening cursors and iterating through data.</p>
<h2 id="heading-how-to-idbkeyrange">How to: IDBKeyRange</h2>
<p>It's common case, when you need to fetch only part of the data. In that case, iterating through all items and filtering them is not the fastest solution.</p>
<p>It's better to use KeyRanges. KeyRange allows you to define the search interval for your keys or indexes. <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange">MDN</a> already has an excellent table with the example:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Range</td><td>Code</td></tr>
</thead>
<tbody>
<tr>
<td>Keys &gt;= x</td><td><code>IDBKeyRange.lowerBound(x)</code></td></tr>
<tr>
<td>Keys &gt; x</td><td><code>IDBKeyRange.lowerBound(x, true)</code></td></tr>
<tr>
<td>Keys &lt;= x</td><td><code>IDBKeyRange.upperBound(x)</code></td></tr>
<tr>
<td>Keys &lt; x</td><td><code>IDBKeyRange.upperBound(x, true)</code></td></tr>
<tr>
<td>y&gt;= Keys &gt;= x</td><td><code>IDBKeyRange.bound(x, y)</code></td></tr>
<tr>
<td>y&gt; Keys &gt; x</td><td><code>IDBKeyRange.bound(x, y, true, true)</code></td></tr>
<tr>
<td>y&gt; Keys &gt; x</td><td><code>IDBKeyRange.bound(x, y, true, true)</code></td></tr>
<tr>
<td>y&gt;= Keys &gt; x</td><td><code>IDBKeyRange.bound(x, y, true, false)</code></td></tr>
<tr>
<td>y&gt; Keys &gt;= x</td><td><code>IDBKeyRange.bound(x, y, false, true)</code></td></tr>
<tr>
<td>only x</td><td>just use <code>x</code> as a key or <code>IDBKeyRange.only(z)</code></td></tr>
</tbody>
</table>
</div><h2 id="heading-how-to-idbindex">How to: IDBIndex</h2>
<p>📝 Provides access to <code>index</code> inside ObjectStore. <code>IDBIndex</code> has all <code>IDBObjectStore</code> methods to read the data with the same API, including <code>IDBCursor(WithValue)</code> requests: <code>count()</code>, <code>get()</code>, <code>getAll()</code>, <code>getAllKeys()</code>, <code>getKey()</code>, <code>openCursor()</code>, <code>openKeyCursor()</code>.</p>
<h1 id="heading-end-of-article-tips">End-of-article tips!</h1>
<p>📝 IndexedDB database is unique by domain. Meaning if you have several sub-domains, you will have separate DBs per domain;</p>
<p>📝 Transaction <code>commit</code>/<code>abort</code> methods don't work for upgrade transaction;</p>
<p>📝 DB connection is not guaranteed to be maintained the whole page lifetime. The DB connection can be lost. You should be subscribed to <code>.onclose</code> event and re-store connection either lazily or proactively as soon as you loose connection</p>
<p>📝 Sometimes you can receive (in sentry or user reports) issues, that Database in not accessible. It's known problem and for users it can be solved only by re-loading the browser (quit and reopen).</p>
<p>📝 As IndexedDB makes structured clone every time you persist / read the data</p>
]]></content:encoded></item><item><title><![CDATA[TL&DRs: What I know about JavaScript String.]]></title><description><![CDATA[Hey! What do you know about string type in JavaScript? I believe you saw gazillion times about 1+'1' or so. In this article, I won't talk about it, but I'd suggest we cover these questions:

How strings are stored?

Is there a limit for the string le...]]></description><link>https://blog.xnim.me/what-i-know-about-javascript-string</link><guid isPermaLink="true">https://blog.xnim.me/what-i-know-about-javascript-string</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[string]]></category><category><![CDATA[heap]]></category><dc:creator><![CDATA[Nik]]></dc:creator><pubDate>Sat, 05 Aug 2023 13:04:05 GMT</pubDate><content:encoded><![CDATA[<p>Hey! What do you know about <code>string</code> type in JavaScript? I believe you saw gazillion times about <code>1+'1'</code> or so. In this article, I won't talk about it, but I'd suggest we cover these questions:</p>
<ol>
<li><p>How strings are stored?</p>
</li>
<li><p>Is there a limit for the string length?</p>
</li>
<li><p>Is it possible to overflow the call stack?</p>
</li>
</ol>
<h2 id="heading-how-are-strings-stored">How are strings stored?</h2>
<p>📝 Strings are stored in Heap. There is a special place for all the strings you're using: string pool:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1691227412368/f829ff47-3683-46a5-a6b1-1b5d2456f292.png" alt class="image--center mx-auto" /></p>
<p>Use DevTools console + memory tab to check how the string pool allocates memory:</p>
<ol>
<li><p>Open <code>about://blank</code> page</p>
</li>
<li><p>Paste code like this (but do not execute it):</p>
</li>
<li><pre><code class="lang-javascript"> <span class="hljs-keyword">const</span> a = <span class="hljs-string">'hello'</span>;
 <span class="hljs-keyword">const</span> b = <span class="hljs-string">'world'</span>;
 <span class="hljs-keyword">const</span> c = a + b;
 <span class="hljs-keyword">const</span> d = <span class="hljs-string">'hello'</span>
</code></pre>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1691227999455/9168b3b6-8757-43a1-8e31-3fcce97db25d.png" alt class="image--center mx-auto" /></p>
<p> Go to the memory tab and take the first heap snapshot. You will get something like this:</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1691229251138/e2fa2aea-4dab-48f9-8b29-4b2f91006b9f.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Execute the pasted code and take the second snapshot. You can compare snapshots using the select menu on top:</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1691229307895/e0a9d5e7-e65d-4c0f-9816-69407f6b440a.png" alt class="image--center mx-auto" /></p>
<p> You can see that <code>hello</code>, <code>world</code> and their concatenation were used only once.</p>
</li>
</ol>
<h3 id="heading-is-there-a-limit-for-the-string-length">Is there a limit for the string length?</h3>
<p>📝 Yes, in V8 (node.js, chrome, etc.) there is a <strong>limit of 512Mb</strong>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1691231503905/36bca473-e603-4110-a690-cc3b39d8f97d.png" alt class="image--center mx-auto" /></p>
<p>It was done by using:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> a = <span class="hljs-built_in">Array</span>.from({<span class="hljs-attr">length</span>: MB64}).join(<span class="hljs-string">'a'</span>)
b = a + a + a + a + a + a + a + a
</code></pre>
<p>Further increase in length would lead to the <code>Invalid string length</code>exception:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1691231866687/eece4107-46b3-45d2-acc2-ac9ed87d31c9.png" alt class="image--center mx-auto" /></p>
<p>📝 Be careful while processing big JSON entities or huge files in Node.JS or Chrome environment. You might need to use streams / binary blobs while reading data, instead of strings.</p>
<p>📝 Firefox at the same time doesn't have such a limit. You can see on a screenshot a string with about 1GB length:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1691232317605/5221fb48-af2d-4176-9f03-4ee7fb45a946.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-is-it-possible-to-overflow-the-stack-using-strings">Is it possible to overflow the stack using strings?</h3>
<p>As strings are stored in a heap, the answer is no.</p>
<p>The only thing how you can overflow the stack is by creating too many references, like that:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1691233512604/586e50c0-4494-4250-b17d-5f3c32bc5fc9.png" alt class="image--center mx-auto" /></p>
<p>More context about this trick: <a target="_blank" href="https://blog.xnim.me/js-call-stack-size-exceeded">https://blog.xnim.me/js-call-stack-size-exceeded</a></p>
]]></content:encoded></item><item><title><![CDATA[Why even simple window.fetch might be tricky]]></title><description><![CDATA[I recently saw this tweet https://twitter.com/thomasfindlay94/status/1672211922517622784:

While many responses were related to missed await before fetch, I'd like to talk about a bigger issue: error handling.
Let's fix an obvious error and take a cl...]]></description><link>https://blog.xnim.me/why-even-simple-window-fetch-might-be-tricky</link><guid isPermaLink="true">https://blog.xnim.me/why-even-simple-window-fetch-might-be-tricky</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[error handling]]></category><category><![CDATA[fetch]]></category><category><![CDATA[fetch API]]></category><dc:creator><![CDATA[Nik]]></dc:creator><pubDate>Mon, 03 Jul 2023 08:45:43 GMT</pubDate><content:encoded><![CDATA[<p>I recently saw this tweet <a target="_blank" href="https://twitter.com/thomasfindlay94/status/1672211922517622784">https://twitter.com/thomasfindlay94/status/1672211922517622784</a>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1687551348958/b34d6f24-4276-4ef9-aff8-290642aca61b.png" alt class="image--center mx-auto" /></p>
<p>While many responses were related to missed <code>await</code> before fetch, I'd like to talk about a bigger issue: error handling.</p>
<p>Let's fix an obvious error and take a closer look at this code block:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> fetchData = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'https://website.com'</span>);
    <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json();
    <span class="hljs-keyword">return</span> data;
}
</code></pre>
<p><code>fetchData</code> returns a <code>Promise</code> with a parsed JSON value (object) inside.</p>
<p>How many error-handling blocks do we need here? Would it be sufficient to have a single try-catch?</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> fetchData = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'https://website.com'</span>);
        <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json();
        <span class="hljs-keyword">return</span> data;
    } <span class="hljs-keyword">catch</span> (e) {
       <span class="hljs-comment">// Error reporting goes here</span>
    }
}
</code></pre>
<p>The short answer here is no, it's not the best possible design.</p>
<p>We have only 1 catch entry point, which should handle all the work:</p>
<p><code>e</code> can be:</p>
<ol>
<li><p>Network issue:</p>
<ol>
<li><p>Unstable connection, e.g. timeout</p>
</li>
<li><p>User goes offline</p>
</li>
<li><p>Other problems like CORS:</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688216718622/c420938d-f2f4-4c06-a390-3ac009e48e67.png" alt="Example of CORS error" class="image--center mx-auto" /></p>
</li>
<li><p>Parsing errors, when the received content is not valid JSON or not a JSON at all</p>
</li>
</ol>
</li>
</ol>
<p>That's the reason why sometimes engineers wrap every <code>await</code> in <code>try-catch</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> fetchData = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">let</span> response; <span class="hljs-comment">// We have to move response on top</span>
    <span class="hljs-keyword">try</span> {
        response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'https://website.com'</span>);
    } <span class="hljs-keyword">catch</span> (e) {
        <span class="hljs-comment">// Handle network errors</span>
        <span class="hljs-keyword">return</span> errorState;
    }
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json();
        <span class="hljs-keyword">return</span> data;
    } <span class="hljs-keyword">catch</span> (e) {
       <span class="hljs-comment">// Handle parsing/serialisation error</span>
    }
}
</code></pre>
<p>Additionally, when we return <code>data</code> we cannot be sure of what would be the output. The request might get an error code response, as the server returns something different from 2xx:</p>
<ol>
<li><p>Sometimes it is worth asking the user to re-login (e.g. 401)</p>
</li>
<li><p>Sometimes we should tell users they have no permission (403)</p>
</li>
<li><p>In some cases, validation might fail (400)</p>
</li>
<li><p>We might want to schedule a retry or ask user to try again later (5xx)</p>
</li>
<li><p>and etc.</p>
</li>
</ol>
<p>And yes, the error code response would fulfill the original <code>await</code> promise:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688216584388/64d3d7c2-1afc-4f73-bda7-573186415bc7.png" alt class="image--center mx-auto" /></p>
<p>It would complicate our code furthermore:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> fetchData = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">let</span> response; <span class="hljs-comment">// We have to move response on top</span>
    <span class="hljs-keyword">try</span> {
        response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'https://website.com'</span>);
    } <span class="hljs-keyword">catch</span> (e) {
        <span class="hljs-comment">// Handle network errors</span>
        <span class="hljs-keyword">return</span> someErrorState;
    }
    <span class="hljs-comment">// Request is successfull if any of the following conditions are true:</span>
    <span class="hljs-comment">// response.ok === true</span>
    <span class="hljs-comment">// response.status === 200</span>
    <span class="hljs-comment">// response.status &gt;= 200 &amp;&amp; response.status &lt; 300</span>
    <span class="hljs-keyword">if</span> (!response.ok) {
        <span class="hljs-keyword">switch</span> (response.status) {
            <span class="hljs-keyword">case</span> <span class="hljs-number">401</span>: {
                <span class="hljs-comment">// Need re-login</span>
                <span class="hljs-keyword">return</span> someErrorState;
            }
            ...
            default: 
                <span class="hljs-comment">// Other errors</span>
                <span class="hljs-keyword">return</span> someErrorState;
        }
    }

    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json();
        <span class="hljs-keyword">return</span> data;
    } <span class="hljs-keyword">catch</span> (e) {
       <span class="hljs-comment">// Handle parsing/serialisation error</span>
    }
}
</code></pre>
<p>This code looks way more like spaghetti rather than something you can read, right?</p>
<p>But let's spend a little bit more time handling the last case: <em>When a server returns an error code, it also can add a payload to identify an error exactly!</em></p>
<p>The most common usage for that is 400 error code during form validations or so. That means we should do <code>response.json()</code> even when <code>response.ok === false</code>. While doing so we should remember that <code>response.json()</code> might reject the promise:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688217418843/cd273d49-8019-4608-9128-e5109fae298b.png" alt class="image--center mx-auto" /></p>
<p>The most "correct" way is to verify <code>content-type</code> header of the <code>Response</code> before calling <code>.json</code> or so. It's stored in <code>Response.headers</code> field:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688217621300/c2659337-f4b5-4d95-8950-24b09480c030.png" alt class="image--center mx-auto" /></p>
<p>However:</p>
<ol>
<li><p>It would over-complicate the code for the cases where we know for sure we expect <code>.json</code> format;</p>
</li>
<li><p>There still can be a "corrupted" json.</p>
</li>
</ol>
<p>Let's make it all together and start brushing up:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseResponseAsJson</span>(<span class="hljs-params">response</span>) </span>{
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json();
        <span class="hljs-keyword">return</span> data;
    } <span class="hljs-keyword">catch</span> (e) {
       <span class="hljs-comment">// Handle parsing/serialisation error</span>
    }
}

<span class="hljs-keyword">const</span> fetchData = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">let</span> response; <span class="hljs-comment">// We have to move response on top</span>
    <span class="hljs-keyword">try</span> {
        response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'https://website.com'</span>);
    } <span class="hljs-keyword">catch</span> (e) {
        <span class="hljs-comment">// Handle network errors</span>
        <span class="hljs-keyword">return</span> someErrorState;
    }
    <span class="hljs-comment">// Request is successfull if any of the following conditions are true:</span>
    <span class="hljs-comment">// response.ok === true</span>
    <span class="hljs-comment">// response.status === 200</span>
    <span class="hljs-comment">// response.status &gt;= 200 &amp;&amp; response.status &lt; 300</span>
    <span class="hljs-keyword">if</span> (!response.ok) {
        <span class="hljs-keyword">switch</span> (response.status) {
            <span class="hljs-keyword">case</span> <span class="hljs-number">400</span>: {
                <span class="hljs-comment">// Error, but we expect JSON with details</span>
                <span class="hljs-keyword">return</span> parseResponseAsJson(response);
            }
            <span class="hljs-keyword">case</span> <span class="hljs-number">401</span>: {
                <span class="hljs-comment">// Need re-login</span>
                <span class="hljs-keyword">return</span> someErrorState;
            }
            ...
            default: 
                <span class="hljs-comment">// Other errors</span>
                <span class="hljs-keyword">return</span> someErrorState;
        }
    }
    <span class="hljs-keyword">return</span> parseResponseAsJson(response);   
}
</code></pre>
<p>Overall we have a huge bunch of code just to make a simple request. That's insane!</p>
<h1 id="heading-how-to-make-it-simpler">How to make it simpler?</h1>
<p>Let's start reducing the complexity of the code.</p>
<p>To do so, we should define the contract first.</p>
<p>We expect <code>fetchData</code> to return some <code>TResponse</code> from the server as a success. Additionally, we have a number of error states, which we need to support. Let's categorize them:</p>
<ol>
<li><p>NetworkError</p>
</li>
<li><p>ParseError</p>
</li>
<li><p>400 error code</p>
</li>
<li><p>Login Error</p>
</li>
<li><p>ServerError</p>
</li>
<li><p>(Something else depending on a task)</p>
</li>
</ol>
<p>For simplicity, we can say we expect only <code>application/json</code> content type.</p>
<p>Here are the steps which we can make:</p>
<ol>
<li><p>Create a clear contract for developers</p>
</li>
<li><p>Combine back 2 <code>await</code> inside single <code>try-catch</code> block. To make it coorect we should distinguish, which line raised an <code>Error</code>. To do so, we should keep <code>response</code> variable out of <code>try-catch</code> block</p>
</li>
<li><p>Keep "a good path" as short as possible and let all the error handling be placed either before or after "a good result"</p>
</li>
<li><p>Give the power to handle error codes to the developer, who would call our method. They should be able to have a <code>switch</code> statement on their side, but <code>json</code> results should be available to them.</p>
</li>
<li><p>[Optional] Add simple typing</p>
</li>
</ol>
<p>Let's implement it:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> ResponseResult&lt;TResponse&gt; =
  | {
      ok: <span class="hljs-literal">true</span>;
      data: TResponse;
    }
  | {
      ok: <span class="hljs-literal">false</span>;
      errorCode: <span class="hljs-built_in">number</span>;
      <span class="hljs-comment">// If you expect in your app particular type of errors,</span>
      <span class="hljs-comment">// you can define errorData, or event specific pairs of</span>
      <span class="hljs-comment">// errorCode+errorData</span>
      errorData: <span class="hljs-built_in">void</span> | unknown;
    }
  | {
      ok: <span class="hljs-literal">false</span>;
      error: <span class="hljs-built_in">any</span>;
    };

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> fetchData = <span class="hljs-keyword">async</span> &lt;TResponse&gt;(
  url: <span class="hljs-built_in">string</span>
): <span class="hljs-built_in">Promise</span>&lt;ResponseResult&lt;TResponse&gt;&gt; =&gt; {
  <span class="hljs-keyword">let</span> response: <span class="hljs-built_in">void</span> | Response; <span class="hljs-comment">// We have to move response on top</span>
  <span class="hljs-keyword">try</span> {
    response = <span class="hljs-keyword">await</span> fetch(url);
    <span class="hljs-keyword">const</span> parsed = <span class="hljs-keyword">await</span> response.json();
    <span class="hljs-keyword">if</span> (response.ok) {
      <span class="hljs-keyword">return</span> {
        ok: <span class="hljs-literal">true</span>,
        data: parsed <span class="hljs-keyword">as</span> TResponse
      };
    }

    <span class="hljs-keyword">return</span> {
      ok: <span class="hljs-literal">false</span>,
      errorCode: response.status,
      errorData: parsed
    };
  } <span class="hljs-keyword">catch</span> (e) {
    <span class="hljs-comment">// we should remember that response might fail due to Network issue</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> response === <span class="hljs-string">"object"</span>) {
      <span class="hljs-keyword">if</span> (response.ok) {
        <span class="hljs-keyword">return</span> {
          ok: <span class="hljs-literal">false</span>,
          error: e
        };
      }
      <span class="hljs-keyword">return</span> {
        ok: <span class="hljs-literal">false</span>,
        errorCode: response.status,
        errorData: <span class="hljs-literal">null</span>
      };
    }
    <span class="hljs-keyword">return</span> {
      ok: <span class="hljs-literal">false</span>,
      error: e
    };
  }
};
</code></pre>
<p>Sandbox: <a target="_blank" href="https://codesandbox.io/s/dank-hooks-6pxyqp">https://codesandbox.io/s/dank-hooks-6pxyqp</a></p>
<p>So... after several changes we finally did the version of <code>fetch</code> which looks way better than at the very beginning, right?</p>
<p>We have error handling, parsing, response processing, and status code support, which should be sufficient for many common <code>fetch</code> cases.</p>
<h2 id="heading-the-journey-isnt-ended-tho">The journey isn't ended tho</h2>
<p>We still owe:</p>
<ol>
<li><p><code>fetch</code> configuration support. It is simple to do, we just need to extend function arguments</p>
</li>
<li><p><code>Abort</code> / <code>AbortController</code> support</p>
</li>
<li><p>Add support for data types different from <code>json</code> for <code>ReadableStream</code>.</p>
</li>
<li><p>XSRF support</p>
</li>
<li><p>Better typing: e.g. better types for expected errors such as 4xx or so</p>
</li>
<li><p>etc.</p>
</li>
</ol>
<p>And after a few more iterations we can get a utility library to handle requests for your project in the right way!</p>
<p>The alternative solution is to use a ready library: <a target="_blank" href="https://github.com/axios/axios">axios</a>, <a target="_blank" href="https://github.com/ladjs/superagent">superagent</a>, <a target="_blank" href="https://github.com/unjs/ofetch">ofetch</a> or similar</p>
]]></content:encoded></item><item><title><![CDATA[Browser Event loop: micro and macro tasks, call stack, render queue: layout, paint, composite]]></title><description><![CDATA[This article was initially published in my custom blog, but since I migrate to hashnode, I re-visited and re-wrote it
The article focuses on the event loop, the order of execution, and how developers can optimise code. The fully detailed schema:

Eve...]]></description><link>https://blog.xnim.me/event-loop-and-render-queue</link><guid isPermaLink="true">https://blog.xnim.me/event-loop-and-render-queue</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Event Loop]]></category><category><![CDATA[repaint and reflow]]></category><category><![CDATA[layout]]></category><category><![CDATA[render queue]]></category><dc:creator><![CDATA[Nik]]></dc:creator><pubDate>Sun, 12 Mar 2023 13:56:47 GMT</pubDate><content:encoded><![CDATA[<p><em>This article was initially published in my custom blog, but since I migrate to hashnode, I re-visited and re-wrote it</em></p>
<p>The article focuses on the event loop, the order of execution, and how developers can optimise code. The fully detailed schema:</p>
<p><img src="https://xnim.me/The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled.png" alt="Detailed schema of event loop" /></p>
<h1 id="heading-event-loop">Event loop</h1>
<p>Old operational systems didn't support multithreading and their event loop can be approximately described as a simple cycle:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) {
    <span class="hljs-keyword">if</span> (execQueue.isNotEmpty()) {
        execQueue.pop().exec();
    }
}
</code></pre>
<p>This code utilises all CPU. It was so in old OS. Modern OS schedulers are utterly complicated. They have prioritisation, execution queues, and many other technologies.</p>
<p>We can start describing the event loop as a cycle, which checks whether we have any pending tasks:</p>
<p><img src="https://xnim.me/The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%201.png" alt="Simple cycle, which checks if we have any tasks to execute" /></p>
<p>To get a task for the execution let's draft</p>
<p>✍️The list of triggers that can put a task into the event loop:</p>
<ol>
<li><p><code>&lt;script&gt;</code> tag</p>
</li>
<li><p>Postponed tasks: <code>setTimeout</code>, <code>setInterval</code>, <code>requestIdleCallback</code></p>
</li>
<li><p>Event handlers from browser API: <code>click</code>, <code>mousedown</code>, <code>input</code>, <code>blur,</code> and etc.</p>
<ol>
<li><p>Some of the events are user-initiated like clicks, tab switching, etc.</p>
</li>
<li><p>Some of them are from our code: <code>XmlHttpRequest</code> response handler, <code>fetch</code> promise resolve, and so on</p>
</li>
</ol>
</li>
<li><p>The promise state change. More about <a target="_blank" href="https://dev.to/xnimorz/101-series-promises-1-how-promises-work-1k1i">promises in my series</a></p>
</li>
<li><p>Observers like <code>DOMMutationObserver</code>, <code>IntersectionObserver</code></p>
</li>
<li><p><code>RequestAnimationFrame</code></p>
</li>
</ol>
<p>Almost everything we described above is planned through <code>WebAPI</code> (or browserAPI).</p>
<p><em>For example,</em> we have such a line on our code: <code>setTimeout(function a() {}, 100)</code><br />When we execute <code>setTimeout</code> the WebAPI postpones the task for 100ms. After 100ms WebAPI puts <code>function a()</code> into the queue. We can call it <code>TaskQueue</code>. <code>EventLoop</code> gets the task on the next cycle iteration and executes it.</p>
<p>We discussed tasks in our event loop. Both our JS code and browser should be able to work with DOM.</p>
<p>Our js code:</p>
<ul>
<li><p>Reads the data of DOM elements: size, attributes, position, etc.</p>
</li>
<li><p>Mutates attributes: data- attr, width, height, position, CSS properties, etc.</p>
</li>
<li><p>Creates / removes HTML nodes</p>
</li>
</ul>
<p>Browsers render the data so that the user can see the updates.</p>
<p>✍️Modern browsers execute both JS and render flow in the same thread. (except the cases, when we create a Web/Shared/Service worker).</p>
<p>That means, that the EventLoop should have "rendering" in the schema. The rendering flow is not a single operation. I'd say, it's <code>render queue</code>:</p>
<p><img src="https://xnim.me/The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%202.png" alt="The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%202.png" /></p>
<p>Now we have 2 sources for the tasks to execute for EventLoop. The first is the RenderQueue and the second one is "SomeJsTasks". Should the browser pick 1 task from the js tasks queue and then render the page? To answer the question let's take a look at the screen updating problem:</p>
<h2 id="heading-screen-updating">Screen updating</h2>
<p>For browsers, the event loop is linked with frames, since EventLoop executes both JS code and renders the page. I'd suggest considering the frame as a single snapshot of the screen state, which a user sees in a moment.</p>
<p>✍️ Browsers are heading to show the updates on a page as quickly as possible, considering existing limits in hardware and software:</p>
<p><strong>Hardware limits</strong>: Screen refresh rate</p>
<p><strong>Software limits</strong>: OS settings, browser, and its settings, energy-saving settings, etc.</p>
<p>✍️ The vast majority of browsers / OS supports 60 FPS (Frames Per Second). Browsers try to update the screen at this particular rate.</p>
<p><em>When we use 60 FPS in the article it's better to keep in mind that we consider the most common frame rate and it could be changed in future</em></p>
<p>It means, that the browsers have timeslots of 16.6 ms (1000/60) for the tasks before they have to render a new frame (and rendering a new frame will also consume time).</p>
<h2 id="heading-task-queue-and-micro-task-queue">Task queue and Micro Task Queue</h2>
<p>Now it's time to decompose "SomeJsTasks" and to understand how it works.</p>
<p>Browsers use 2 queues to execute our code:</p>
<ol>
<li><p><code>Task Queue</code> or <code>Macro Task Queue</code> is dedicated to all events, postponed tasks, etc.</p>
</li>
<li><p><code>Micro Task Queue</code> is for promise callbacks: both resolved and rejected, and for <code>MutationObserver</code>. The single element from this queue is "Micro Task".</p>
</li>
</ol>
<p>Now let's take a look at both of them:</p>
<h3 id="heading-task-queue">Task queue</h3>
<p>When the browser receives a new task, it puts the task into <code>Task Queue</code>. Each cycle <code>Event Loop</code> takes the task from the <code>Task Queue</code> and executes it. After the task is done, if the browser has time (the render queue has no tasks) <code>Event Loop</code> gets another task from <code>Task Queue</code>, and another task till the render queue receives a task to execute.</p>
<p><em>The first example:</em></p>
<p><img src="https://xnim.me/The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%203.png" alt="The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%203.png" /></p>
<p>We have 3 tasks: A, B, C. Event loop gets the first one and executes it. It takes 4 ms. Then the event loop checks other queues (Micro Task Queue and Render Queue). They are empty. Event Loop executes Task B. It takes 12 ms. In total two tasks use 16 ms. Then the browser adds tasks to Render Queue to draw a new frame. The event loop checks the render queue and starts the execution of tasks in the render queue. They take 1 ms approx. After these operations Event loop returns to TaskQueue and executes the last task C.</p>
<p>The event loop can't predict how much time a task will be executed. Furthermore, the event loop isn't able to pause the task to render the frame, as the browser engine doesn't know if it can draw changes from custom JS code or if it is some kind of preparation and not the final state. We just don't have an API for this.</p>
<p>✍️ During JS code execution all the changes which JS makes, won't be presented as a rendered frame to the user until the macro task and all pending micro-tasks are completed. However, JS code can calculate the DOM changes.</p>
<p><em>The second example:</em></p>
<p><img src="https://xnim.me/The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%204.png" alt="The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%204.png" /></p>
<p>We have only 2 tasks in the queue (A, B). The first task A takes 240ms. As 60FPS means that each frame should be rendered every 16.6ms, the browser loses approximately 14 frames. When task A ends the event loop executes tasks from the render queue to draw the new frame. <em>Important note</em>:  Even though we lost 14 frames it doesn't mean we will render 15 frames in a row. It will be a single frame.</p>
<p>Before reviewing Micro Task Queue, let's talk about the call stack.</p>
<h3 id="heading-call-stack">Call Stack</h3>
<p>✍️ The call stack is a list that shows which functions with arguments are currently being called and where the transition will take place when the current function finishes the execution.</p>
<p>Let's look at the example:</p>
<pre><code class="lang-jsx"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">findJinny</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">debugger</span>;
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Dialog with Jinny'</span>);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">goToTheCave</span>(<span class="hljs-params"></span>) </span>{
  findJinny();
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">becomeAPrince</span>(<span class="hljs-params"></span>) </span>{
  goToTheCave();  
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">findAFriend</span>(<span class="hljs-params"></span>) </span>{
   <span class="hljs-comment">// ¯\_(ツ)_/¯</span>
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">startDndGame</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> friends = [];
  <span class="hljs-keyword">while</span> (friends.length &lt; <span class="hljs-number">2</span>) {
    friends.push(findAFriend());
  }
  becomeAPrince();
}
<span class="hljs-built_in">console</span>.log(startDndGame());
</code></pre>
<p>This code will be paused on the debugger instruction.</p>
<p>We start our stack from inline code: <code>console.log(startDndGame());</code> . it is the start of the call stack. Generally, chrome points out the reference to this line. Let's mark it as <code>inline</code>. Then we go down to the <code>startDndGame</code> function and <code>findAFriend</code> is called several times. This function wouldn't be presented in the call stack as it is ended before we get to the <code>debugger</code>. That's how the call stack looks like when we stop at <code>debugger</code>:</p>
<p><img src="https://xnim.me/The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%205.png" alt="The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%205.png" /></p>
<p>✍️When the call stack gets empty, the current task is done.</p>
<h3 id="heading-what-are-microtasks">What are microtasks?</h3>
<p>There are only 2 possible sources of micro tasks: Promise callbacks (<code>onResolved/onRejected</code>) and MutationObserver callbacks.</p>
<p>Microtasks have one main feature which makes them completely different:</p>
<p>✍️The microtask will be executed as soon as the call stack becomes empty.</p>
<p>Microtasks can create other microtasks which will be executed when the call stack ends. Each new microtask postpones the execution of a new macro task or the new frame rendering.</p>
<p><em>Let's check the example, where We have 4 microtasks in the micro task queue:</em></p>
<p><img src="https://xnim.me/The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%206.png" alt="The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%206.png" /></p>
<p>The first micro task to execute is <code>A</code>. <code>A</code> takes 200ms, and we have tasks in render queue. However, they will be postponed because we still have 3 more tasks in micro task queue. It means that after <code>A</code> Event loop takes micro task <code>B</code>, <code>C</code> and finally <code>D</code>. When the micro task queue gets empty, the event loop renders a new frame. In the example these 4 microtasks take 0.5 seconds to complete. All this time the browser UI was blocked and non-interactive.</p>
<p>✍️ Subsequent micro-tasks can block the website UI and make the page non-interactive.</p>
<p>This micro-task feature could be both advantage and a disadvantage. For example, when MutationObserver calls its callback as per DOM changes, the user won't see the changes on the page before the callback completes. Thereby, we can effectively manage the content which the user sees.</p>
<p>The updated event loop schema:</p>
<p><img src="https://xnim.me/The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%207.png" alt="The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%207.png" /></p>
<h2 id="heading-what-is-executed-inside-the-render-queue">What is executed inside the render queue?</h2>
<p>Frame rendering is not a single operation. Frame rendering has several stages. Each stage can be divided into substages. Here is the base schema of how a new frame gets rendered:</p>
<p><img src="https://xnim.me/The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%208.png" alt="The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%208.png" /></p>
<p>Let's dwell on each stage in more detail:</p>
<h3 id="heading-request-animation-frame-raf">Request Animation Frame (RAF)</h3>
<p><img src="https://xnim.me/The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%209.png" alt="The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%209.png" /></p>
<p>The browser is ready to start rendering, we can subscribe to it and calculate or prepare the frame for the animation step. This callback suits well for working with animations or planning some changes in DOM right before the frame gets rendered.</p>
<p>✍️ Some interesting facts about RAF:</p>
<ol>
<li><p>RAF's callback has an argument <code>DOMHighResTimeStamp</code> which is the number of milliseconds passed since "time origin" which is the start of the document's lifetime. You may not need to use <code>performance.now()</code> inside the callback, you already have it;</p>
</li>
<li><p>RAF returns a descriptor (id), hence you can cancel RAF callback using cancelAnimationFrame. (like setTimeout);</p>
</li>
<li><p>If a user changes the tab or minimized the browser, you won't have a re-render which means you won't have RAF either;</p>
</li>
<li><p>JS code that changes the size of the elements or reads element properties may force requestAnimationFrame;</p>
</li>
<li><p>Safari call(ed) RAF after frame rendered. This is the only browser with different behavior. <a target="_blank" href="https://github.com/whatwg/html/issues/2569#issuecomment-332150901">https://github.com/whatwg/html/issues/2569#issuecomment-332150901</a></p>
</li>
<li><p>How to check how often the browser renders frames? This code would help:</p>
</li>
</ol>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> checkRequestAnimationDiff = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">let</span> prev;
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">call</span>(<span class="hljs-params"></span>) </span>{
        requestAnimationFrame(<span class="hljs-function">(<span class="hljs-params">timestamp</span>) =&gt;</span> {
            <span class="hljs-keyword">if</span> (prev) {
                <span class="hljs-built_in">console</span>.log(timestamp - prev); 
                <span class="hljs-comment">// It should be around 16.6 ms for 60FPS</span>
            }
            prev = timestamp;
            call();
        });
    }
    call();
}
checkRequestAnimationDiff();
</code></pre>
<p>Here is the usage example:</p>
<p><img src="https://xnim.me/The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%2010.png" alt="The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%2010.png" /></p>
<h3 id="heading-style-recalculation">Style (recalculation)</h3>
<p><img src="https://xnim.me/The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%2011.png" alt="The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%2011.png" /></p>
<p>✍️The browser recalculates styles that should be applied. This step also calculates which media queries will be active.</p>
<p>The recalculations include both direct changes <code>a.styles.left = '10px'</code> and those described through CSS files, such as <code>element.classList.add('my-styles-class')</code> They will all be recalculated in terms of CSSOM and Render tree production.</p>
<p>If you run the profiler and open the hashnode.com website, this is where you can find the time spent on Style:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678229031084/f1c022de-e95d-4ee3-a572-f5d802d15c19.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-layout">Layout</h3>
<p>✍️Calculating layers, element positions, their size, and their mutual influence on each other. The more DOM elements on the page the harder the operation is.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678229432884/a8b9d9f8-1aa8-4a1c-b80c-e473cd46d2a9.png" alt class="image--center mx-auto" /></p>
<p>Layout is quite a painful operation for modern websites. Layout happens every time when you:</p>
<ol>
<li><p>Read properties associated with the size and position of the element (<code>offsetWidth</code>, <code>offsetLeft</code>, <code>getBoundingClientRect</code>, etc.)</p>
</li>
<li><p>Write properties associated with the size and position of the elements except some of them (like <code>transform</code> and <code>will-change</code>). <code>transform</code> operates in <code>composition</code> process. <code>will-change</code> would signal to the browser, that changing the property should be calculated in <code>composition</code> stage. Here you can check the actual list of the reasons for that: <a target="_blank" href="https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.cc;l=39">https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.cc;l=39</a></p>
</li>
</ol>
<p>Layout is in charge of:</p>
<ol>
<li><p>Calculating layouts</p>
</li>
<li><p>Elements interposition on the layer</p>
</li>
</ol>
<p>✍️ Layout (with or without RAF or style) can be executed when js has resized elements or read properties. This process is called <code>force layout</code> The full list of properties that forces Layout: <a target="_blank" href="https://gist.github.com/paulirish/5d52fb081b3570c81e3a">https://gist.github.com/paulirish/5d52fb081b3570c81e3a</a>.</p>
<p>✍️ When layout is forced, browser paused JS main thread despite the call stack isn't empty.</p>
<p>Let's check it on the example:</p>
<pre><code class="lang-javascript">div1.style.height = <span class="hljs-string">"200px"</span>; <span class="hljs-comment">// Change element size</span>
<span class="hljs-keyword">var</span> height1 = div1.clientHeight; <span class="hljs-comment">// Read property</span>
</code></pre>
<p>Browser cannot calculate <code>clientHeight</code> of our <code>div1</code> without recalculating its real size. In this case, the browser paused JS execution and runs: Style to check what should be changed, and Layout to recalculate sizes. Layout calculates not only elements that are placed before our <code>div1</code>, but after as well. Modern browsers optimize calculation so that you won't recalculate the whole dom tree each time, but we still have it in bad cases. The process of recalculation is called <code>Layout Shift</code>. You can check it on the screenshot and see that you have the list of the elements which will be modified and shifted during layout:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678229829284/302a4dc6-e895-40ad-bff4-936265a37ad6.png" alt class="image--center mx-auto" /></p>
<p>Browsers try not to force layout each time. So they group operations:</p>
<pre><code class="lang-javascript">div1.style.height = <span class="hljs-string">"200px"</span>;
<span class="hljs-keyword">var</span> height1 = div1.clientHeight; <span class="hljs-comment">// &lt;-- layout 1</span>
div2.style.margin = <span class="hljs-string">"300px"</span>;
<span class="hljs-keyword">var</span> height2 = div2.clientHeight; <span class="hljs-comment">// &lt;-- layout 2</span>
</code></pre>
<p>On the first line browser plans height changed.<br />On the second line, browser receives a request to read the property. As we have pending height changes, browser has to force layout.<br />The same situation we have on 3rd + 4th lines. To make it better for browsers we can group read and write operations:</p>
<pre><code class="lang-javascript">div1.style.height = <span class="hljs-string">"200px"</span>;
div2.style.margin = <span class="hljs-string">"300px"</span>;
<span class="hljs-keyword">var</span> height1 = div1.clientHeight; <span class="hljs-comment">// &lt;-- layout 1</span>
<span class="hljs-keyword">var</span> height2 = div2.clientHeight;
</code></pre>
<p>By grouping elements, we get rid of the second layout, because when browser reaches the 4th line it already has all the data.</p>
<p>Our event loop mutates from only one loop to several as we can force layout on both tasks and microtask stages:</p>
<p><img src="https://xnim.me/The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%2016.png" alt="The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%2016.png" /></p>
<p>Some advice on how to optimize layout:</p>
<ol>
<li><p>Reduce the DOM nodes number</p>
</li>
<li><p>Group read \ write operations to get rid of unnecessary layouts</p>
</li>
<li><p>Replace operations that force layout with operations that force composite</p>
</li>
</ol>
<h3 id="heading-paint">Paint</h3>
<p><img src="https://xnim.me/The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%2017.png" alt="The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%2017.png" /></p>
<p>✍️ We have the element, its position on a viewport, and its size. Now we have to apply color, background that is to say to "draw" it</p>
<p><img src="https://xnim.me/The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%2018.png" alt="The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%2018.png" /></p>
<p>This operation usually doesn't consume lots of time, however, it may be big during the first render. After this step, we are able to "physically" draw the frame. The latest operation is "Composition".</p>
<h3 id="heading-composition">Composition</h3>
<p><img src="https://xnim.me/The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%2019.png" alt="The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%2019.png" /></p>
<p>✍️ Composition is the only stage that runs on GPU by default. In this step browser executes only specific CSS styles like "transform".</p>
<p><strong>Important note:</strong> <code>transform: translate</code> doesn't "turn on" the render on a GPU. So, if you have <code>transform: translateZ(0)</code> in your codebase to move the render on a GPU, it doesn't work in such a way. It's a misconception.</p>
<p>Modern browsers can move part of the operation to the GPU on their own. I didn't find the up-to-date list for that, so it's better to check in source code: <a target="_blank" href="https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.cc;l=39">https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.cc;l=39</a></p>
<p><img src="https://xnim.me/The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%2020.png" alt="The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%2020.png" /></p>
<p>✍️ <code>transform</code> is the best choice for complex animations:</p>
<ol>
<li><p>We don't force layout each frame, we save CPU time</p>
</li>
<li><p>These animations a free from artifacts ("soap"): small lags which you may follow when website has animations implemented through top, right, bottom, left.</p>
</li>
</ol>
<h3 id="heading-how-to-optimize-render">How to optimize render?</h3>
<p>✍️ The most difficult operation for frame rendering is the layout. When you have a complex animation, each render may require shifting all the DOM elements that are ineffective, as you'd spend 13-20ms (or even more). You will lose frames and hence, your website performance.</p>
<p>To improve the performance you can skip some of the rendering stages:</p>
<p><img src="https://xnim.me/The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%2021.png" alt="The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%2021.png" /></p>
<p>✍️ We may skip the layout phase if we change colours, background image, etc.</p>
<p><img src="https://xnim.me/The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%2022.png" alt="The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%2022.png" /></p>
<p>✍️ We can drop layout and paint when we use <code>transform</code> and we don't read properties from our DOM elements. You may cache them and store them in the memory.</p>
<p>✍️ <strong>Summing up, here are some advice:</strong></p>
<ol>
<li><p>Move animations from JS to CSS. Running additional JS code is not "for free"</p>
</li>
<li><p>Animate <code>transform</code> for "moving" objects</p>
</li>
<li><p>Use <code>will-change</code> property. It allows browsers to "prepare" DOM elements for the property mutations. This property just helps browsers to see, that developer is about to change it. <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/will-change">https://developer.mozilla.org/en-US/docs/Web/CSS/will-change</a></p>
</li>
<li><p>Use batch changes for DOM</p>
</li>
<li><p>Use requestAnimationFrame to plan changes in the next frame</p>
</li>
<li><p>Combine read \ write element CSS properties operations, and use memoization.</p>
</li>
<li><p>Pay attention to properties that force layout: <a target="_blank" href="https://gist.github.com/paulirish/5d52fb081b3570c81e3a">https://gist.github.com/paulirish/5d52fb081b3570c81e3a</a></p>
</li>
<li><p>When you have a non-trivial situation it's better to run the profiler and check frequency and timings. It gives you the data that phase is slow.</p>
</li>
<li><p>Optimize step-by-step, do not try to do everything at once.</p>
</li>
</ol>
<h1 id="heading-how-does-event-loop-look-like-in-the-end">How does Event Loop look like in the end:</h1>
<p><img src="https://xnim.me/The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%2016.png" alt="The%20long%20journey%20to%20the%20runtime%20Part%203%20Event%20loop,%2005203256cba04d22bc89656fdf50f252/Untitled%2016.png" /></p>
<p>If we open <a target="_blank" href="https://github.com/w3c/longtasks/blob/loaf-explainer/loaf-explainer.md#the-current-situation">https://github.com/w3c/longtasks/blob/loaf-explainer/loaf-explainer.md#the-current-situation</a> we can see the code which represents modern browsers Event Loop:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) {
    <span class="hljs-keyword">const</span> taskStartTime = performance.now();
    <span class="hljs-comment">// It's unspecified where UI events fit in. Should each have their own task?</span>
    <span class="hljs-keyword">const</span> task = eventQueue.pop();
    <span class="hljs-keyword">if</span> (task)
        task.run();
    <span class="hljs-keyword">if</span> (performance.now() - taskStartTime &gt; <span class="hljs-number">50</span>)
        reportLongTask();

    <span class="hljs-keyword">if</span> (!hasRenderingOpportunity())
        <span class="hljs-keyword">continue</span>;

    invokeAnimationFrameCallbacks();
    <span class="hljs-keyword">while</span> (needsStyleAndLayout()) {
        styleAndLayout();
        invokeResizeObservers();
    }
    markPaintTiming();
    render();
}
</code></pre>
]]></content:encoded></item><item><title><![CDATA[JS: Call stack size exceeded]]></title><description><![CDATA[Some time ago I was working with big arrays of about 2.000.000 elements. One of the operations was to find the maximum value in the array.
Sounds simple, right? We can use Max.max:
const arr = new Array(2000000);
...
const max = Math.max(...arr);

Ho...]]></description><link>https://blog.xnim.me/js-call-stack-size-exceeded</link><guid isPermaLink="true">https://blog.xnim.me/js-call-stack-size-exceeded</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Spread operator]]></category><category><![CDATA[callstack]]></category><category><![CDATA[call stack]]></category><dc:creator><![CDATA[Nik]]></dc:creator><pubDate>Sun, 12 Mar 2023 13:44:36 GMT</pubDate><content:encoded><![CDATA[<p>Some time ago I was working with big arrays of about 2.000.000 elements. One of the operations was to find the maximum value in the array.</p>
<p>Sounds simple, right? We can use Max.max:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> arr = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Array</span>(<span class="hljs-number">2000000</span>);
...
const max = <span class="hljs-built_in">Math</span>.max(...arr);
</code></pre>
<p>However, if you try to execute it, you will have an error:</p>
<p><img src="https://xnim.me/Math%20max%20maximum%20call%20stack%20size%20exceeded%20%5Bru%5D%20db4a5ad20eda400f9ae083b75022bc30/Untitled.png" alt="Math%20max%20maximum%20call%20stack%20size%20exceeded%20%5Bru%5D%20db4a5ad20eda400f9ae083b75022bc30/Untitled.png" /></p>
<p>Looks weird... Probably the issue is with the spread operator?</p>
<p><img src="https://xnim.me/Math%20max%20maximum%20call%20stack%20size%20exceeded%20%5Bru%5D%20db4a5ad20eda400f9ae083b75022bc30/Untitled%201.png" alt="Math%20max%20maximum%20call%20stack%20size%20exceeded%20%5Bru%5D%20db4a5ad20eda400f9ae083b75022bc30/Untitled%201.png" /></p>
<p>No, it's not. So why do we have such an error?</p>
<p>The first thing I was thinking of is that the Math.max uses recursion.</p>
<p>However, it looks weird, that v8 developers could use recursion for that. Let's check it in v8 sources: <a target="_blank" href="https://github.com/v8/v8/blob/dc712da548c7fb433caed56af9a021d964952728/src/builtins/math.tq#L134-L144">https://github.com/v8/v8/blob/dc712da548c7fb433caed56af9a021d964952728/src/builtins/math.tq#L134-L144</a></p>
<pre><code class="lang-jsx"><span class="hljs-comment">// ES6 #sec-math.max</span>
extern macro Float64Max(float64, float64): float64;
transitioning javascript builtin
MathMax(js-implicit context: NativeContext)(...arguments): <span class="hljs-built_in">Number</span> {
  <span class="hljs-keyword">let</span> result: float64 = MINUS_V8_INFINITY;
  <span class="hljs-keyword">const</span> argCount = <span class="hljs-built_in">arguments</span>.length;
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i: intptr = <span class="hljs-number">0</span>; i &lt; argCount; i++) {
    <span class="hljs-keyword">const</span> doubleValue = TruncateTaggedToFloat64(<span class="hljs-built_in">arguments</span>[i]);
    result = Float64Max(result, doubleValue);
  }
  <span class="hljs-keyword">return</span> Convert&lt;<span class="hljs-built_in">Number</span>&gt;(result);
}
</code></pre>
<p>Of course, we have recursion here, but the depth of it is only 2 functions. The first one is MathMax and the second one compares exactly 2 values.</p>
<p>Maybe I have an old v8 version? We can dig into 2014-2015 chrome versions: <a target="_blank" href="https://chromium.googlesource.com/v8/v8/+/4.3.21/src/math.js?autodive=0%2F%2F#86">https://chromium.googlesource.com/v8/v8/+/4.3.21/src/math.js?autodive=0%2F%2F#86</a> and we can see that the old implementation doesn't have recursion:</p>
<pre><code class="lang-jsx"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MathMax</span>(<span class="hljs-params">arg1, arg2</span>) </span>{  <span class="hljs-comment">// length == 2</span>
  <span class="hljs-keyword">var</span> length = %_ArgumentsLength();
  <span class="hljs-keyword">if</span> (length == <span class="hljs-number">2</span>) {
    arg1 = TO_NUMBER_INLINE(arg1);
    arg2 = TO_NUMBER_INLINE(arg2);
    <span class="hljs-keyword">if</span> (arg2 &gt; arg1) <span class="hljs-keyword">return</span> arg2;
    <span class="hljs-keyword">if</span> (arg1 &gt; arg2) <span class="hljs-keyword">return</span> arg1;
    <span class="hljs-keyword">if</span> (arg1 == arg2) {
      <span class="hljs-comment">// Make sure -0 is considered less than +0.</span>
      <span class="hljs-keyword">return</span> (arg1 === <span class="hljs-number">0</span> &amp;&amp; %_IsMinusZero(arg1)) ? arg2 : arg1;
    }
    <span class="hljs-comment">// All comparisons failed, one of the arguments must be NaN.</span>
    <span class="hljs-keyword">return</span> NAN;
  }
  <span class="hljs-keyword">var</span> r = -INFINITY;
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i = <span class="hljs-number">0</span>; i &lt; length; i++) {
    <span class="hljs-keyword">var</span> n = %_Arguments(i);
    <span class="hljs-keyword">if</span> (!IS_NUMBER(n)) n = NonNumberToNumber(n);
    <span class="hljs-comment">// Make sure +0 is considered greater than -0.</span>
    <span class="hljs-keyword">if</span> (NUMBER_IS_NAN(n) || n &gt; r || (r === <span class="hljs-number">0</span> &amp;&amp; n === <span class="hljs-number">0</span> &amp;&amp; %_IsMinusZero(r))) {
      r = n;
    }
  }
  <span class="hljs-keyword">return</span> r;
}
</code></pre>
<h2 id="heading-so-what-was-the-problem">So, what was the problem?</h2>
<p>To find this out we can use the best tool which developers have. Let's take a long patient look once again:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> arr = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Array</span>(<span class="hljs-number">2000000</span>);
...
const max = <span class="hljs-built_in">Math</span>.max(...arr);
</code></pre>
<p>How do we call this function?</p>
<p>When we call a function that has 2 Number arguments:</p>
<pre><code class="lang-jsx"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">test</span>(<span class="hljs-params">a, b</span>) </span>{
}
test(<span class="hljs-number">1</span>,<span class="hljs-number">2</span>);
</code></pre>
<p>The arguments, which are primitive types, are transmitted by their values. We will write to the call stack 2 numbers: 1 and 2.</p>
<p>Now, let's call the function using the reference (object) type:</p>
<pre><code class="lang-jsx"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">test</span>(<span class="hljs-params">a, b</span>) </span>{
}
test(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Array</span>(<span class="hljs-number">2000000</span>));
</code></pre>
<p>In this case, we create an array for 2.000.000 elements. Array elements are stored in the heap, and therefore the function receives only the reference to the array.</p>
<p><em>If you're interested in getting more information about Primitive and Reference types you can check this article:</em> <a target="_blank" href="https://dev.to/xnimorz/javascript-memory-management-101-strong-and-weak-refs-finalizationregistry-1281"><em>https://dev.to/xnimorz/javascript-memory-management-101-strong-and-weak-refs-finalizationregistry-1281</em></a></p>
<p>📝 When we use apply or spread operator javascript converts each element from your Iterable object to a separate argument. And therefore you may pass not a reference, but a lot of primitive values.</p>
<p>When we call this code:</p>
<p><img src="https://xnim.me/Math%20max%20maximum%20call%20stack%20size%20exceeded%20%5Bru%5D%20db4a5ad20eda400f9ae083b75022bc30/Untitled%202.png" alt="Math%20max%20maximum%20call%20stack%20size%20exceeded%20%5Bru%5D%20db4a5ad20eda400f9ae083b75022bc30/Untitled%202.png" /></p>
<p>In the third example, we transmit a million numbers as primitives to our test function. We try to write every single array element to the call stack. More info about the call stack: <a target="_blank" href="https://en.wikipedia.org/wiki/Call_stack#Structure">https://en.wikipedia.org/wiki/Call_stack#Structure</a></p>
<p>It's how we exceeded the call stack limit. Be careful with the spread operator 🙈</p>
]]></content:encoded></item></channel></rss>