<?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[musings]]></title><description><![CDATA[musings]]></description><link>https://koundinya.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 29 Apr 2026 04:30:36 GMT</lastBuildDate><atom:link href="https://koundinya.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Caching is not cheating: Perceived performance wins]]></title><description><![CDATA[Your app feels slow. You did all the optimizations in your code imaginable. You curbed the re-renders, lazy loaded the components, memoized where necessary. And yet, people say: “Hey, it is slow.”
They aren't necessarily saying your UI is janky. What...]]></description><link>https://koundinya.dev/caching-is-not-cheating-perceived-performance-wins</link><guid isPermaLink="true">https://koundinya.dev/caching-is-not-cheating-perceived-performance-wins</guid><category><![CDATA[perceived-performance]]></category><category><![CDATA[React]]></category><category><![CDATA[performance]]></category><category><![CDATA[react-query]]></category><category><![CDATA[user experience]]></category><category><![CDATA[swr]]></category><dc:creator><![CDATA[Koundinya Gavicherla]]></dc:creator><pubDate>Sun, 20 Apr 2025 13:10:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1745152961058/62c467ed-ace9-4e57-9b32-07c265764fcd.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Your app feels slow. You did all the optimizations in your code imaginable. You curbed the re-renders, lazy loaded the components, memoized where necessary. And yet, people say: “Hey, it is slow.”</p>
<p>They aren't necessarily saying your UI is janky. What they might mean is: <em>"I clicked on a button and nothing happened for a while."</em></p>
<p>Here, the culprit could be <strong>slow APIs</strong>, worsened by network latency or connectivity issues. And when the UI does not respond immediately, the app <em>feels</em> broken, even if it is not.</p>
<p>This is the realm of <strong>perceived performance</strong>: how fast our app <em>feels</em>, regardless of how fast it <em>is</em>.</p>
<p>Imagine user taps a button and sees a blank screen with a spinner for 1.5 seconds. That is enough for them to feel something is wrong. But maybe the API was just taking its sweet time. Or maybe... we refetched data unnecessarily. In most cases API is not even the problem. the problem lies in how we used this API in our app.</p>
<h2 id="heading-the-hidden-cost-of-slow-apis">The Hidden Cost of Slow APIs</h2>
<p>Assuming the below user journey.<br />1. User lands on customer customer page<br />2. <code>CustomerList</code> API returns the response in 2 sec(it needs to aggregate data from different microservices etc)<br />3. User sees a spinner on this page for 2 sec or more.<br />4. User clicks on customer item<br />5. Customer details are loaded<br />6. User clicks back and then the spinner shows again</p>
<p>If the app always shows a loading spinner even when the user is revisiting the same screen within seconds, it <em>feels</em> broken, even though it is technically working as intended. This constant refetching of data will make our UI feel sluggish.</p>
<h2 id="heading-so-what-can-we-do-in-this-scenario">So, what can we do in this scenario?</h2>
<p>Instead of fetching the customer list from scratch every time, we can cache the data once it is loaded the first time. When the user navigates back, we can immediately display the cached list and silently revalidate it in the background if needed. This approach avoids unnecessary spinners, maintains a smooth experience, and helps the app feel far more responsive, even if the underlying APIs are still slow.</p>
<h2 id="heading-caching-to-the-rescue">Caching to the Rescue</h2>
<p>We can reduce this "waiting time" by using <strong>simple in-memory caching</strong></p>
<h3 id="heading-step-1-basic-fetch">Step 1: Basic Fetch</h3>
<pre><code class="lang-javascript">useEffect(<span class="hljs-function">() =&gt;</span> {
  fetch(<span class="hljs-string">'/api/user'</span>)
    .then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> res.json())
    .then(setUser);
}, []);
</code></pre>
<p>This works, but fetches fresh data every time the component mounts. Even if the data hasn't changed.</p>
<h3 id="heading-step-2-add-a-manual-cache">Step 2: Add a Manual Cache</h3>
<pre><code class="lang-javascript">useEffect(<span class="hljs-function">() =&gt;</span> {
  fetchWithCache(<span class="hljs-string">'user'</span>, <span class="hljs-string">'/api/user'</span>).then(setUser);
}, []);
</code></pre>
<p>This shows cached data immediately if available. Huge win for perceived speed</p>
<h3 id="heading-step-3-implement-stale-while-revalidateswr">Step 3: Implement Stale-While-Revalidate(SWR)</h3>
<pre><code class="lang-javascript">useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> cached = cache.get(<span class="hljs-string">'user'</span>);
  <span class="hljs-keyword">if</span> (cached) setUser(cached);

  fetch(<span class="hljs-string">'/api/user'</span>)
    .then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> res.json())
    .then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> {
      cache.set(<span class="hljs-string">'user'</span>, data);
      setUser(data);
    });
}, []);
</code></pre>
<p>This pattern shows old data immediately, while updating it silently in the background. To the user, it feels blazing fast.</p>
<h2 id="heading-scaling-this-write-a-custom-hook">Scaling This: Write a Custom Hook</h2>
<p>While the manual SWR logic is great for one off cases, maintaining this pattern across multiple APIs can quickly get out of hand. Rewriting cache checks, fetch logic, and background updates in every component is repetitive and error-prone.</p>
<p>A better approach is to extract this into a reusable hook, say <code>useCachedFetch</code>, which encapsulates the caching logic and ensures consistency across the app. It can:</p>
<ul>
<li><p>Accept a cache key and URL</p>
</li>
<li><p>Return cached data immediately</p>
</li>
<li><p>Trigger background refetch</p>
</li>
<li><p>Optionally expose metadata like last updated time or fetch status</p>
</li>
</ul>
<p>This gives us all the perceived performance benefits while keeping components clean and declarative.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> cache = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>();

<span class="hljs-keyword">const</span> useCachedFetch = <span class="hljs-function">(<span class="hljs-params">key, url</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> [data, setData] = useState(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> [isLoading, setIsLoading] = useState(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> [lastUpdated, setLastUpdated] = useState(<span class="hljs-literal">null</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> cached = cache.get(key);
    <span class="hljs-keyword">if</span> (cached) {
      setData(cached.data);
      setLastUpdated(cached.timestamp);
    }

    setIsLoading(<span class="hljs-literal">true</span>);
    fetch(url)
      .then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> res.json())
      .then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
        cache.set(key, { <span class="hljs-attr">data</span>: result, <span class="hljs-attr">timestamp</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>() });
        setData(result);
        setLastUpdated(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>());
      })
      .finally(<span class="hljs-function">() =&gt;</span> setIsLoading(<span class="hljs-literal">false</span>));
  }, [key, url]);

  <span class="hljs-keyword">return</span> { data, isLoading, lastUpdated };
}
</code></pre>
<h2 id="heading-why-do-all-this-manually">Why Do All This Manually?</h2>
<p>If we are building a simple app, this approach is great for learning. But caching gets messy <em>fast</em>:</p>
<ul>
<li><p>How long should data stay cached?</p>
</li>
<li><p>What if the data changes?</p>
</li>
<li><p>What about pagination?</p>
</li>
<li><p>How do we handle failed requests?</p>
</li>
<li><p>What if the user goes offline?</p>
</li>
</ul>
<p>That’s a lot of responsibility. And frankly, it has been solved already.</p>
<h2 id="heading-meet-react-query-smart-caching-without-the-pain">Meet React Query: Smart Caching Without the Pain</h2>
<p>Let’s face it, writing caching logic for every API is tiring. It’s doable, but repetitive. And when you want things like retries, background refetching, or pagination? It gets messy real fast.</p>
<p>That’s where <strong>React Query</strong> steps in. It’s a battle-tested library built to take care of data fetching and caching, the stuff we just spent all that time manually handling.</p>
<p>Here’s what using it looks like:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { data, isLoading } = useQuery([<span class="hljs-string">'user'</span>], <span class="hljs-function">() =&gt;</span> 
  fetch(<span class="hljs-string">'/api/user'</span>).then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> res.json())
);
</code></pre>
<p>That one line gives you:</p>
<ul>
<li><p><strong>Automatic caching</strong>: React Query remembers your data and reuses it across navigations.</p>
</li>
<li><p><strong>Stale-while-revalidate</strong>: It shows stale data immediately and refreshes in the background.</p>
</li>
<li><p><strong>Background refetching</strong>: Triggers updates when the window refocuses or after intervals.</p>
</li>
<li><p><strong>Retry on failure</strong>: It will retry failed requests automatically with smart backoff logic.</p>
</li>
<li><p><strong>Pagination &amp; infinite queries</strong>: Built-in tools to handle lists, pages, and scroll.</p>
</li>
<li><p><strong>Status indicators</strong>: Easily access <code>isLoading</code>, <code>isFetching</code>, <code>isError</code>, <code>isSuccess</code>.</p>
</li>
<li><p><strong>Query invalidation</strong>: Refetch only the data you need after a mutation.</p>
</li>
</ul>
<p>All of this means your app <em>feels</em> fast and reliable and you do not have to reinvent the wheel.</p>
<p>React Query removes the heavy lifting, but does not get in your way. It is flexible, intuitive, and gives you control when you want it.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Improving perceived performance is often about <em>showing something useful faster</em>, not <em>getting data faster</em>.</p>
<p>We can absolutely build caching ourselves. But if the app is growing or and we want to focus on features, we are better off using react-query. users do not care what tech/library we use. all they care is “<em>Can I quickly get my stuff done</em>” And that’s what matters.</p>
]]></content:encoded></item><item><title><![CDATA[Should I Stream JSON or Poll JSON: A Practical Take]]></title><description><![CDATA[Streaming vs. Polling JSON: Choosing the Right Approach for Rubric Generation
When designing the rubric generation system, I faced a key architectural decision:

Should I stream JSON responses as they are generated, or should I poll the backend for u...]]></description><link>https://koundinya.dev/should-i-stream-json-or-poll-json-a-practical-take</link><guid isPermaLink="true">https://koundinya.dev/should-i-stream-json-or-poll-json-a-practical-take</guid><category><![CDATA[edtech]]></category><category><![CDATA[#AIinEducation]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[insights]]></category><category><![CDATA[Grading]]></category><dc:creator><![CDATA[Koundinya Gavicherla]]></dc:creator><pubDate>Thu, 06 Feb 2025 13:13:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1738846742769/b9e28c51-da70-4b98-80da-338d75c0d996.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-streaming-vs-polling-json-choosing-the-right-approach-for-rubric-generation"><strong>Streaming vs. Polling JSON: Choosing the Right Approach for Rubric Generation</strong></h2>
<p>When designing the <strong>rubric generation system</strong>, I faced a key architectural decision:</p>
<blockquote>
<p><strong>Should I stream JSON responses as they are generated, or should I poll the backend for updates?</strong></p>
</blockquote>
<p>Streaming sounds like the obvious choice for real-time applications, but after careful consideration, I chose <strong>polling</strong>. Here’s why.</p>
<h2 id="heading-understanding-the-problem-rubric-generation-is-an-asynchronous-process"><strong>Understanding the Problem: Rubric Generation is an Asynchronous Process</strong></h2>
<p>Generating rubrics is not an instant operation. It involves multiple steps, each happening asynchronously:</p>
<ol>
<li><p><strong>Analyze the uploaded question paper.</strong></p>
</li>
<li><p><strong>Extract questions and transform them into JSON.</strong></p>
</li>
<li><p><strong>Send each JSON block to OpenAI for evaluation (create tasks for OpenAI to generate rubrics for each question).</strong></p>
</li>
<li><p><strong>Store responses in the database for retrieval.</strong></p>
</li>
</ol>
<p>Since each JSON response is generated independently, <strong>streaming might seem like a good fit</strong>. But does it actually make sense?</p>
<h2 id="heading-challenges-with-streaming-json"><strong>Challenges with Streaming JSON</strong></h2>
<p>If I were to stream rubric data to the frontend, several key questions arose:</p>
<h3 id="heading-1-what-if-the-user-navigates-away-or-kills-the-app"><strong>1) What if the user navigates away or kills the app?</strong></h3>
<ul>
<li><p><strong>Streaming requires an active connection.</strong> If the user leaves mid-process, they could <strong>lose all streamed data.</strong></p>
</li>
<li><p>Polling allows users to <strong>resume tracking progress anytime</strong>, even after navigating away.</p>
</li>
</ul>
<h3 id="heading-2-when-should-we-insert-data-into-the-database"><strong>2) When should we insert data into the database?</strong></h3>
<ul>
<li><p>Should we <strong>stream first</strong> and insert later? If so, what happens if the database rejects some updates?</p>
</li>
<li><p>Should we <strong>insert first</strong> and then stream? If so, how do we ensure updates are pushed properly?</p>
</li>
</ul>
<h3 id="heading-3-what-if-one-process-breaks"><strong>3) What if one process breaks?</strong></h3>
<ul>
<li><p>If <strong>data insertion and streaming run separately</strong>, one could fail while the other succeeds.</p>
</li>
<li><p>This could lead to users seeing rubric updates <strong>that don’t actually exist in the database</strong>.</p>
</li>
<li><p>To avoid this, I would need to <strong>set up queues, event listeners, and retry mechanisms</strong>, making the infrastructure much more complex.</p>
</li>
</ul>
<p>At this point, <strong>streaming JSON became a rabbit hole of complexity.</strong> I realized that I would need to:<br />1. Maintain a <strong>queue</strong> to persist data while streaming.<br />2. Set up <strong>event listeners</strong> to detect failures and retry processing.<br />3. Ensure that streaming doesn’t <strong>outpace database updates</strong>.</p>
<p>This added <strong>unnecessary complexity</strong> at both the infrastructure and implementation levels.</p>
<h2 id="heading-polling-json-a-more-predictable-amp-reliable-approach"><strong>Polling JSON: A More Predictable &amp; Reliable Approach</strong></h2>
<p>While polling is often seen as an "archaic" approach, it actually <strong>provides predictability and consistency</strong> in this case.</p>
<p>Here’s how the <strong>rubric generation flow</strong> works with polling:</p>
<ol>
<li><p><strong>User uploads a question paper.</strong></p>
</li>
<li><p><strong>The system extracts questions and converts them into JSON.</strong></p>
</li>
<li><p><strong>A background job is started</strong> to generate rubrics for the entire question set.</p>
</li>
<li><p><strong>As the LLM generates rubrics for each question, we insert them into the database</strong> under a common <strong>parent ID</strong>.</p>
</li>
<li><p><strong>Once all questions have been processed, the job is marked as finished.</strong></p>
</li>
<li><p><strong>The frontend polls an API</strong> that aggregates all rubrics linked to the <strong>parent ID</strong> and displays them in real time.</p>
</li>
</ol>
<h2 id="heading-why-polling-wins-in-this-scenario"><strong>Why Polling Wins in This Scenario</strong></h2>
<p><strong>More Reliable:</strong> Polling always retrieves data from the database, ensuring consistency.<br /><strong>Handles User Navigation:</strong> Users can leave and return at any time without losing progress.<br /><strong>Simpler Infrastructure:</strong> No need for persistent connections, queues, or event-driven architecture.<br /><strong>Predictable Execution:</strong> Each poll fetches exactly what’s in the database—nothing more, nothing less.</p>
<h2 id="heading-final-thoughts-occams-razor-in-action"><strong>Final Thoughts: Occam’s Razor in Action</strong></h2>
<p>Occam’s Razor states:<br /><em>"The simplest solution that meets all requirements is usually the best."</em></p>
<p><strong>Polling ensures real-time updates while avoiding the complexities of streaming, queue management, and event handling.</strong></p>
<p>For use cases like <strong>chat apps or stock tickers</strong>, <strong>streaming makes sense</strong>. But for a job-based system like rubric generation, <strong>polling is the better choice.</strong>  </p>
<p>A short video of rubrics polling in action.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=yPOsxMKzxVk">https://www.youtube.com/watch?v=yPOsxMKzxVk</a></div>
]]></content:encoded></item><item><title><![CDATA[Why am I building Traceblade: Motivation and MVP Progress]]></title><description><![CDATA[User event tracking and logging is one of the most crucial instrument for understanding user behaviour and ensuring system stability. In my day-to-day job I found myself juggling multiple tools to connect the dots between a user event and related err...]]></description><link>https://koundinya.dev/why-am-i-building-traceblade-motivation-and-mvp-progress</link><guid isPermaLink="true">https://koundinya.dev/why-am-i-building-traceblade-motivation-and-mvp-progress</guid><category><![CDATA[user events]]></category><category><![CDATA[analytics]]></category><category><![CDATA[logging]]></category><dc:creator><![CDATA[Koundinya Gavicherla]]></dc:creator><pubDate>Mon, 06 Jan 2025 11:32:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736161079716/b4b820b6-4b26-4c75-9684-4adc896f9584.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>User event tracking and logging is one of the most crucial instrument for understanding user behaviour and ensuring system stability. In my day-to-day job I found myself juggling multiple tools to connect the dots between a user event and related error. This process was not only time consuming but also frustrating, as the data was scattered across various platforms.<br />While building <strong>AI Assisted Grading Tool ,</strong> I searched for solutions that could provide both user events and error logs(this is the main focus) in one place- <strong>a unified view</strong>. Unfortunately, I couldn’t find a tool that met this need without breaking the bank.<br />Thats when I realised it would be much more efficient(and cost-effective) to build a system tailored to this exact problem- one that bridges the gap between user event tracking and logging. as someone balancing the roles of a product manager and a developer for AI Assisted Grading Tool, I knew this tool could save me time and effort while solving a widespread challenge.</p>
<p><strong>Building the MVP</strong></p>
<p>With Traceblade, I set out to bridge the gap between user events and logs. The first version focuses mainly on the fundamentals: capturing user events and logs independently. While the unified view is still in progress, the foundation is a critical step.</p>
<p><strong>Things I needed for a solid foundation.</strong></p>
<ol>
<li><p><strong>An ingestion pipeline</strong></p>
<p> The main focus is not just to ingest data into the system but to do it seamlessly. As my project uses React Native and Go, it was only natural that I created a <strong>React Native SDK</strong> and <strong>Go-Gin middleware</strong>, which need to be initialized with an API key configured in the Traceblade project settings.</p>
</li>
<li><p><strong>The Interface</strong></p>
<p> For the MVP, I opted for <strong>separate views (events and logs)</strong>, as this allows me to build a solid foundation before tackling the complexities of a <strong>unified experience</strong>.</p>
<p> <strong>Natural Language Querying</strong></p>
<p> One of my favorite features is <strong>natural language querying</strong>, which will make it easy to search and filter data using simple, human-readable queries. This feature, demonstrated briefly at the <a class="post-section-overview" href="#">14-second mark of the video,</a> this can go beyond raw data visualization and provide actionable insights.</p>
</li>
</ol>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=UDDF18GYPeE">https://www.youtube.com/watch?v=UDDF18GYPeE</a></div>
<p> </p>
<h2 id="heading-the-next-steps"><strong>The Next Steps</strong></h2>
<p>So far, I have captured the basics, but there is still so much more to do. My focus for the next milestones includes:</p>
<ul>
<li><p><strong>Unified Timeline View</strong>: Combining user events and logs into a single interface to understand the ripple effects of user interactions across services.</p>
</li>
<li><p><strong>Advanced Filtering</strong>: Allowing users to fine-tune searches across events and logs.</p>
</li>
<li><p><strong>User Paths/Journeys</strong>: Analyzing and presenting user journeys within an app.</p>
</li>
<li><p><strong>Scalability</strong>: Ensuring Traceblade can handle larger datasets efficiently.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Configureable code: the good, bad and the ugly parts.]]></title><description><![CDATA[Introduction
When we talk about 'configuration over code', we're essentially looking at a different way of controlling how things work in our applications. Instead of hard-coding every detail, we use external configurations. This shift can make our c...]]></description><link>https://koundinya.dev/configureable-code-the-good-bad-and-the-ugly-parts</link><guid isPermaLink="true">https://koundinya.dev/configureable-code-the-good-bad-and-the-ugly-parts</guid><category><![CDATA[codeconfigurations]]></category><category><![CDATA[React]]></category><category><![CDATA[components]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[configuration]]></category><category><![CDATA[ui-composition]]></category><category><![CDATA[clean code]]></category><category><![CDATA[performance]]></category><dc:creator><![CDATA[Koundinya Gavicherla]]></dc:creator><pubDate>Tue, 12 Dec 2023 06:30:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1702339436207/4c45f399-8c0f-4dab-a745-cc1c33572254.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>When we talk about 'configuration over code', we're essentially looking at a different way of controlling how things work in our applications. Instead of hard-coding every detail, we use external configurations. This shift can make our code more modular, easier to read, and, let's not forget, scalable.</p>
<p>In this piece, we are going to dive into how this concept of 'configuration over code' can be applied to the basic elements we use every day in building applications. Although this idea can fit into any framework, we will be using React as our go-to for examples – it's just easier to explain things this way. So, let's get started and see how this approach can change the way we write code.</p>
<h2 id="heading-the-good-unlocking-flexibility">The Good: Unlocking Flexibility</h2>
<p>We all strive to write code that's not just neat and efficient, but also easy to modify. To achieve this, we often organize related logic in one place and extract overly generic logic into handy helper functions. This is a great start, but let's take it a step further. Imagine if we could compose our components in such a way that their behavior is dictated by the configurations we provide. With this setup, any new behavior or modification could be managed through the config, rather than tinkering with the component itself.</p>
<p>Wouldn't this make our lives easier? Instead of delving into the nitty-gritty of the component code every time we need a change, we simply adjust the configurations. This method not only streamlines the development process but also opens up a world of flexibility and scalability. Let’s delve into how this approach can revolutionize the way we build and maintain our applications.</p>
<p>To illustrate this idea let's assume we are building a user profile form with the below personas and each user has different types of fields.</p>
<ul>
<li><p>Student</p>
</li>
<li><p>Professional</p>
</li>
<li><p>Artists</p>
</li>
</ul>
<p>In a traditional approach, we would make forms for all these individual users separately and display the ones needed by using conditionals. it sounds cumbersome, if a new persona gets added one would have to repeat the cycle of creating form and adding additional logic. There is a better way.</p>
<p><strong>Configuration over code approach</strong></p>
<p>Instead of creating individual forms, we create one dynamic form that consumes a config and renders the labels and fields based on the config. this way when a new persona is added, or a field needs to be updated one needs to just update the config and it will be done.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> formConfig = {
    <span class="hljs-attr">student</span>: [
        { <span class="hljs-attr">name</span>: <span class="hljs-string">'name'</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'text'</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">'Full Name'</span>, <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span> },
        { <span class="hljs-attr">name</span>: <span class="hljs-string">'age'</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'number'</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">'Age'</span> },
        { <span class="hljs-attr">name</span>: <span class="hljs-string">'school'</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'text'</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">'School Name'</span>, <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span> },
    ],
    <span class="hljs-attr">professional</span>: [
        { <span class="hljs-attr">name</span>: <span class="hljs-string">'name'</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'text'</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">'Full Name'</span>, <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span> },
        { <span class="hljs-attr">name</span>: <span class="hljs-string">'company'</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'text'</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">'Company Name'</span>, <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span> },
        { <span class="hljs-attr">name</span>: <span class="hljs-string">'position'</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'text'</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">'Position'</span> },
    ],
    <span class="hljs-attr">artist</span>: [
        { <span class="hljs-attr">name</span>: <span class="hljs-string">'name'</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'text'</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">'Full Name'</span>, <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span> },
        { <span class="hljs-attr">name</span>: <span class="hljs-string">'medium'</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'text'</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">'Medium'</span> },
        { <span class="hljs-attr">name</span>: <span class="hljs-string">'portfolio'</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'url'</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">'Portfolio URL'</span> },
    ]
};
</code></pre>
<p>A DynamicForm component looks like this</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> DynamicForm = <span class="hljs-function">(<span class="hljs-params">{ userType, onSubmit }</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> fields = formConfig[userType] || [];

    <span class="hljs-keyword">const</span> handleSubmit = <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
        event.preventDefault();
    };

    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleSubmit}</span>&gt;</span>
            {fields.map(field =&gt; (
                <span class="hljs-tag">&lt;<span class="hljs-name">FormField</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{field.name}</span> {<span class="hljs-attr">...field</span>} /&gt;</span>
            ))}
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Submit<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
    );
};

<span class="hljs-keyword">const</span> FormField = <span class="hljs-function">(<span class="hljs-params">{ name, type, label, required }</span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">{name}</span>&gt;</span>{label}<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">{type}</span> <span class="hljs-attr">id</span>=<span class="hljs-string">{name}</span> <span class="hljs-attr">name</span>=<span class="hljs-string">{name}</span> <span class="hljs-attr">required</span>=<span class="hljs-string">{required}</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    );
};
</code></pre>
<p>and the usage would look like this</p>
<pre><code class="lang-javascript">&lt;DynamicForm 
    userType=<span class="hljs-string">"student"</span> 
    onSubmit={handleStudentSubmit} 
/&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">DynamicForm</span> 
    <span class="hljs-attr">userType</span>=<span class="hljs-string">"professional"</span> 
    <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleProfessionalSubmit}</span> 
/&gt;</span></span>
</code></pre>
<p>in this approach updating the form fields or labels or even adding a new persona becomes a matter of just providing config, one doesn't have to touch DynamicForm's logic. When the components are so modularized, testing these components becomes much easier. if you notice this structure encourages us to write our presentation layer and logic separately encouraging separation of concerns idealogy.</p>
<p>There are numerous cases where we might need to show different renderers which are unrelated to each other. let us suppose we have a build a dashboard that shows different widgets: charts, news feeds, weather information etc and each widget has a different way of presenting data. an innocent way of this is to create a separate component for each widget type and use conditional logic in a parent component to decide which widget to render based on the data type.</p>
<p>Instead, we create a single <code>Dashboard</code> component that dynamically renders widgets based on a configuration object. This configuration defines which renderer (component) to use for each widget type</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> widgetConfig = {
    <span class="hljs-attr">chart</span>: {
        <span class="hljs-attr">component</span>: ChartWidget,
        <span class="hljs-attr">props</span>: { },
    },
    <span class="hljs-attr">news</span>: {
        <span class="hljs-attr">component</span>: NewsWidget,
        <span class="hljs-attr">props</span>: { },
    },
    <span class="hljs-attr">weather</span>: {
        <span class="hljs-attr">component</span>: WeatherWidget,
        <span class="hljs-attr">props</span>: { },
    },
};
</code></pre>
<p>Dynamic Dashboard component</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> Dashboard = <span class="hljs-function">(<span class="hljs-params">{ widgets }</span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
            {widgets.map(widget =&gt; {
                const WidgetComponent = widgetConfig[widget.type].component;
                const widgetProps = widgetConfig[widget.type].props;
                return <span class="hljs-tag">&lt;<span class="hljs-name">WidgetComponent</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{widget.id}</span> {<span class="hljs-attr">...widgetProps</span>} {<span class="hljs-attr">...widget.data</span>} /&gt;</span>;
            })}
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    );
};
</code></pre>
<p>Usage:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> dashboardWidgets = [
    { <span class="hljs-attr">id</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'chart'</span>, <span class="hljs-attr">data</span>: {  }},
    { <span class="hljs-attr">id</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'news'</span>, <span class="hljs-attr">data</span>: {  }},
    { <span class="hljs-attr">id</span>: <span class="hljs-number">3</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'weather'</span>, <span class="hljs-attr">data</span>: { }},
];

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Dashboard</span> <span class="hljs-attr">widgets</span>=<span class="hljs-string">{dashboardWidgets}</span> /&gt;</span></span>
</code></pre>
<p>This approach allows for high flexibility and scalability. When we need to add a new widget type or modify an existing one, we can simply update the <code>widgetConfig</code> object. There's no need to rewrite or modify the <code>Dashboard</code> component's logic. Each widget component is responsible for rendering its content based on the props it receives, making the system modular and maintainable.</p>
<p>By using configuration objects to map data types to specific renderers, we separate the concern of what content is displayed from how it is displayed. This leads to a cleaner, more organized codebase that is easier to extend and manage</p>
<h2 id="heading-the-bad-the-config-conundrum">The Bad: The Config Conundrum</h2>
<p>while configuration over code offers significant benefits in terms of flexibility and maintainability, it does come with some drawbacks. knowing these drawbacks will be crucial in making an informed choice of when to do it and when not to.</p>
<ul>
<li><p>Like with any system or thought there is a learning curve involved. For new devs joining the team, understanding a system driven by configs can be daunting. It would require them to have a grasp on both the codebase and the configuration setup</p>
</li>
<li><p>maintainers and developers need to actively keep in mind and have a track if some configuration is being duplicated as after as configurations increase in an application</p>
</li>
<li><p>Interconnected or nested configs can become challenging to handle, main and debug.</p>
</li>
<li><p>With great power comes great responsibility. The flexibility to change the components or even applications behaviour through configuration can open the doors to misconfigurations which could lead to errors that might not be too easy to catch.</p>
</li>
<li><p>Depending on how configurations are structured the performance of the application might take a hit. if heavy configs are provided at the start of the applications it might impact response time.</p>
</li>
<li><p>As we aim to dynamically render components based on configuration might also lead to a less optimized rendering path compared to static component structure.</p>
</li>
</ul>
<h2 id="heading-the-ugly-the-hidden-costs-of-configs">The Ugly: The Hidden Costs of Configs</h2>
<ul>
<li><p><strong>Over-Engineering and Configuration Bloat:</strong> Overuse of configurations can lead to complex and unwieldy configuration objects floating in all scopes of the application, making the code hard to manage and understand. This excessive complexity compromises maintainability and clarity.</p>
</li>
<li><p><strong>Performance Overheads</strong>: Heavy reliance on configurations can introduce performance bottlenecks, especially if configurations are loaded or parsed at runtime, potentially slowing down the application's response time and efficiency</p>
</li>
</ul>
<h3 id="heading-key-takeaways">Key Takeaways</h3>
<ul>
<li><p><strong>Enhanced Flexibility and Scalability</strong>: The configuration-over-code approach allows for easy modifications and scalability through external configurations, reducing the need for direct code changes.</p>
</li>
<li><p><strong>Maintenance and Reusability</strong>: Simplifies updates and maintenance, promoting modularity and reusability, while ensuring separation of concerns in the codebase.</p>
</li>
<li><p><strong>Complexity and Learning Curve</strong>: As it introduces complexities few developers might not be at ease at first, hence incrementally introducing it in smaller chunks is the best way to get people used to complex ideas.</p>
</li>
<li><p><strong>Performance:</strong> this can lead to performance bottlenecks if not managed efficiently, bloating of configuration would be an overhead on the performance.</p>
</li>
<li><p><strong>Balance is Key</strong>: Strike a balance between flexibility and maintainability, plan carefully on when we might need configurable code or components</p>
</li>
</ul>
<h3 id="heading-conclusion-your-insights-matter"><strong>Conclusion: Your Insights Matter</strong></h3>
<p>As we conclude our discussion on 'Configurable code', I invite you to share your experiences and thoughts. Have you encountered challenges or discovered effective strategies? Let’s continue this conversation in the comments. Your perspectives are invaluable in enriching this dialogue.</p>
<p>Looking forward to your contributions!</p>
]]></content:encoded></item><item><title><![CDATA[Unlocking Asynchronicity in JavaScript: From Callbacks to Async/Await]]></title><description><![CDATA[Introduction
Asynchronous JavaScript is a cornerstone of modern web development, enabling efficient and responsive applications. This approach allows web pages to process lengthy operations, such as API calls or data fetching, without stalling the us...]]></description><link>https://koundinya.dev/unlocking-asynchronicity-in-javascript-from-callbacks-to-asyncawait</link><guid isPermaLink="true">https://koundinya.dev/unlocking-asynchronicity-in-javascript-from-callbacks-to-asyncawait</guid><category><![CDATA[asyc]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[asynchronous]]></category><category><![CDATA[await]]></category><category><![CDATA[callbacks]]></category><category><![CDATA[callback functions]]></category><category><![CDATA[Callbacks and Promises]]></category><dc:creator><![CDATA[Koundinya Gavicherla]]></dc:creator><pubDate>Sun, 10 Dec 2023 22:35:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1702250202845/4a2fbafa-5df2-4fff-aa04-a34339d140ca.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-introduction"><strong>Introduction</strong></h3>
<p>Asynchronous JavaScript is a cornerstone of modern web development, enabling efficient and responsive applications. This approach allows web pages to process lengthy operations, such as API calls or data fetching, without stalling the user interface. Traditionally, JavaScript executed code line by line, leading to potential delays in user experience. Asynchronous JavaScript, however, permits operations to run in the background, improving performance and user engagement. This post will delve into the mechanisms that make this possible, from callbacks, a foundational concept, to Promises and Async/Await, which offer more streamlined and readable code structures. Understanding these concepts is crucial for any developer looking to create dynamic, responsive web applications.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">the scope of this article is to showcase what asynchronous javascript is. we will talk about the event loop in a future post.</div>
</div>

<h3 id="heading-synchronous-vs-asynchronous-javascript"><strong>Synchronous vs. Asynchronous JavaScript</strong></h3>
<p>In synchronous execution, code runs line by line, each line waiting for the previous one to finish. If a line of code requires a time-consuming task, the entire application waits.</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Start'</span>);
alert(<span class="hljs-string">'Wait for user to close this alert box'</span>);
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'End'</span>);
</code></pre>
<p>Asynchronous execution allows JavaScript to perform long network requests or timers without blocking the main thread</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Start'</span>);
<span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Timeout finished'</span>);
}, <span class="hljs-number">2000</span>);
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'End'</span>);
<span class="hljs-comment">//output</span>
-&gt; Start
-&gt; End
-&gt; Timeout Finished
</code></pre>
<p>In this case, 'End' is logged before the timeout finishes, illustrating JavaScript's non-blocking nature. This is crucial for tasks like fetching data from an API or reading files, where waiting for the operation to complete would otherwise freeze the UI, leading to a poor user experience. This asynchronous behavior is enabled by the event loop in the JavaScript runtime, which manages the execution of callbacks and integration with Web APIs.</p>
<h3 id="heading-callbacks"><strong>Callbacks</strong></h3>
<p>Callbacks are functions passed as arguments into another function, to be executed later. They enable asynchronous operations in JavaScript, like handling the completion of an event or a response from an API call.</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchData</span>(<span class="hljs-params">number, callback</span>) </span>{
  <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
    callback(<span class="hljs-string">'Data received'</span>, number);
  }, <span class="hljs-number">2000</span>);
}
<span class="hljs-keyword">const</span> myCallback = <span class="hljs-function">(<span class="hljs-params">data, args</span>) =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(data, args)
}
fetchData(<span class="hljs-number">10</span>, myCallback);
</code></pre>
<p>here <em>fetchData</em> accepts a number and functions as an argument. the second argument, which is function is invoked after 2 seconds. this way we defer a task to be done in the future once data is received or a condition is met. In this example, the condition is that we wait for 2 seconds.</p>
<p>let's take the below three API calls and see how we can fetch data that are dependent on each other.</p>
<ol>
<li><p><strong>First API Call</strong>: Fetch user data based on a user ID.</p>
</li>
<li><p><strong>Second API Call</strong>: Use the user's data to fetch their preferences.</p>
</li>
<li><p><strong>Third API Call</strong>: Use the user's preferences to fetch personalized content.</p>
</li>
</ol>
<pre><code class="lang-javascript">getUser(userId, <span class="hljs-function"><span class="hljs-params">user</span> =&gt;</span> {
  getUserPreferences(user, <span class="hljs-function"><span class="hljs-params">preferences</span> =&gt;</span> {
    getPersonalizedContent(preferences, <span class="hljs-function"><span class="hljs-params">content</span> =&gt;</span> {
      <span class="hljs-comment">// do something</span>
    });
  });
});
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">where ever we find ourselves, passing a callback inside another callback. its time to <em>promisify</em> things.</div>
</div>

<p>In a callback structure, each subsequent API call would be nested within the callback of the previous one. This leads to deeply nested, hard-to-read code, often referred to as "callback hell." This structure makes error handling and code maintenance challenging, as the complexity increases with each additional dependent operation.</p>
<h2 id="heading-promises"><strong>Promises</strong></h2>
<p>Promises in JavaScript are objects representing the eventual completion or failure of an asynchronous operation. They have three states:</p>
<ol>
<li><p><strong>Pending</strong>: Initial state, neither fulfilled nor rejected.</p>
</li>
<li><p><strong>Fulfilled</strong>: The operation was completed successfully.</p>
</li>
<li><p><strong>Rejected</strong>: The operation failed.</p>
</li>
</ol>
<p>Error handling with Promises is done using the <code>.catch()</code> method, which catches any errors that occur during the promise chain. Refactoring the earlier example with Promises:</p>
<pre><code class="lang-javascript">getUser(userId)
  .then(<span class="hljs-function"><span class="hljs-params">user</span> =&gt;</span> getUserPreferences(user))
  .then(<span class="hljs-function"><span class="hljs-params">preferences</span> =&gt;</span> getPersonalizedContent(preferences))
  .then(<span class="hljs-function"><span class="hljs-params">content</span> =&gt;</span> {
    <span class="hljs-comment">// Process content</span>
  })
  .catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> {
    <span class="hljs-comment">// Handle any error from the entire chain</span>
  });
</code></pre>
<p>In this structure, each API call returns a Promise. The <code>.then()</code> method handles the successful response, and <code>.catch()</code> manages errors, making the code more readable and easier to maintain.</p>
<h3 id="heading-asyncawait"><strong>Async/Await</strong></h3>
<p>Async/Await, introduced in ES2017, revolutionizes how we write and read asynchronous JavaScript. By prefixing a function with <code>async</code>, we can use the <code>await</code> keyword within it, allowing us to write asynchronous code as if it were synchronous. This approach simplifies handling asynchronous operations, like API calls, by eliminating the complexity of promise chains. Instead of using <code>.then()</code> and <code>.catch()</code>, Async/Await enables error handling through familiar try-catch blocks, enhancing readability and error management. This syntactic sugar not only makes the code cleaner but also easier to understand and maintain.</p>
<p>the above asynchronous tasks which were chained together earlier looks like they are happening one after the other, this makes it makes our code more readable and maintainable. this code above shows how we can make dependent calls look synchronous with await in try block and catch any error in error block.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchUserContent</span>(<span class="hljs-params">userId</span>) </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> getUser(userId);
    <span class="hljs-keyword">const</span> preferences = <span class="hljs-keyword">await</span> getUserPreferences(user);
    <span class="hljs-keyword">const</span> content = <span class="hljs-keyword">await</span> getPersonalizedContent(preferences);
    <span class="hljs-comment">// Process content</span>
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-comment">// Handle errors</span>
  }
}
</code></pre>
<h3 id="heading-practical-applications"><strong>Practical Applications</strong></h3>
<ol>
<li><p><strong>API Requests</strong>: Asynchronous techniques are pivotal in web development for fetching data from servers without freezing the UI. For instance, using the Fetch API, developers can retrieve data from a REST API and display it on the webpage without needing a page to reload.</p>
</li>
<li><p><strong>User Interface (UI) Interactions</strong>: Async operations enhance user experience, like dynamically loading images or content as the user scrolls, keeping the interface responsive and engaging.</p>
</li>
<li><p><strong>Real-Time Applications</strong>: Asynchronous JavaScript is crucial in applications requiring real-time data, like chat applications or live updates. WebSockets, combined with Async/Await, allow for smooth real-time communication.</p>
</li>
<li><p><strong>File Operations (Node.js)</strong>: In server-side JavaScript (Node.js), asynchronous file operations are essential to prevent blocking the main process. Reading or writing files asynchronously ensures server responsiveness.</p>
</li>
<li><p><strong>Animations and Timed Actions</strong>: Async patterns are used in setting up animations or actions that need to be executed after a delay, like using <code>setTimeout</code> for delaying a function execution or creating timed animations.</p>
</li>
</ol>
<h3 id="heading-conclusion"><strong>Conclusion</strong></h3>
<p>we've explored various methods of writing asynchronous JavaScript, from callbacks to Promises, and the more recent Async/Await syntax. These tools are essential in modern web development for creating responsive, non-blocking user interfaces and handling complex operations like API requests, real-time updates, and more. However, it's important to note that the real hero behind all these functionalities is the JavaScript event loop. This fundamental part of the JavaScript runtime environment enables asynchronous behavior, orchestrating the execution of all these operations. While this post focused on the 'how' of writing asynchronous JavaScript, the event loop is the underlying mechanism that makes it all possible.</p>
<h3 id="heading-resources-and-readings"><strong>Resources and Readings</strong></h3>
<ul>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous">https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous</a></p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function">https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function</a></p>
</li>
</ul>
]]></content:encoded></item></channel></rss>