<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: spyke</title>
    <description>The latest articles on DEV Community by spyke (@spyke).</description>
    <link>https://dev.to/spyke</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F49945%2Fc757cfe4-53b7-44b8-8401-3fe5a8c836bd.jpg</url>
      <title>DEV Community: spyke</title>
      <link>https://dev.to/spyke</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/spyke"/>
    <language>en</language>
    <item>
      <title>A Look Back at Outsourcing in the Age of AI</title>
      <dc:creator>spyke</dc:creator>
      <pubDate>Mon, 02 Mar 2026 22:05:36 +0000</pubDate>
      <link>https://dev.to/spyke/a-look-back-at-outsourcing-in-the-age-of-ai-3n2l</link>
      <guid>https://dev.to/spyke/a-look-back-at-outsourcing-in-the-age-of-ai-3n2l</guid>
      <description>&lt;p&gt;Now that I am no longer working in outsourcing, it feels like the right moment to reflect on that industry (and IT consultancy more broadly). The timing couldn't be better -- we've entered the Age of Agentic AI. The similarities are so striking that it’s almost amusing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Promise of AI-Native Companies
&lt;/h2&gt;

&lt;p&gt;In 2025, companies around the world began declaring themselves AI-native and AI-first. Token farms started consuming the majority of new investments. Then came the agents.&lt;/p&gt;

&lt;p&gt;Why would anyone need an agent?&lt;/p&gt;

&lt;p&gt;Because it lets you shift your position on the output–cost curve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Increase output without hiring&lt;/strong&gt;, reducing costs per unit of work.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Maintain output with fewer employees&lt;/strong&gt;, reducing labor costs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scale output quickly&lt;/strong&gt; by investing capital rather than waiting through the long hiring cycle.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hiring takes time. Building a team takes even longer. Now you can transfer money and get a ready-to-work "team." Sounds amazing.&lt;/p&gt;

&lt;p&gt;But we've had this capability for decades. It's called &lt;strong&gt;outsourcing&lt;/strong&gt; and &lt;strong&gt;outstaffing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can buy services that range from a black-box "click a button to build a website" solution to having a person working in your office.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Reputation Gap
&lt;/h2&gt;

&lt;p&gt;Unlike AI agents, which are currently surrounded by optimism, outsourcing has long been infamous -- especially among smaller companies.&lt;/p&gt;

&lt;p&gt;Startup incubators and accelerators tell founders to build teams strictly in-house. Investors push management to get rid of contractors to improve valuation.&lt;/p&gt;

&lt;p&gt;Looking for a job with only outsourcing experience can be an uphill battle. I've heard countless variations of:&lt;/p&gt;

&lt;p&gt;"I liked your answers, but I don't think you'd be able to do this job because you don't have product experience."&lt;/p&gt;

&lt;p&gt;Why does this perception persist?&lt;/p&gt;

&lt;p&gt;Were there bad outsourcing companies that damaged the industry's reputation? Probably. But is that so different from today's "slope"?&lt;/p&gt;

&lt;p&gt;There are bad engineers and there are bad models. We still assume that AI agents are useful. So logically, outsourcing must also have good players.&lt;/p&gt;

&lt;h2&gt;
  
  
  Life as a "Third Party"
&lt;/h2&gt;

&lt;p&gt;Here's what I saw over more than a decade on the other side.&lt;/p&gt;

&lt;p&gt;In theory, you're helping others succeed. In practice, you're often treated as a third party.&lt;/p&gt;

&lt;p&gt;Remember investors encouraging companies to build only in-house teams? That mindset permeates daily operations.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Limited permissions&lt;/strong&gt;: Sometimes it's forbidden to even message another employee on Slack to resolve an issue.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tools&lt;/strong&gt;: Many licenses are reserved for employees.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Production access&lt;/strong&gt;: Connect to the DB, run a pipeline? No -- security concerns.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;KPIs and goal meetings&lt;/strong&gt;: Not for contractors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Infrastructure cost data&lt;/strong&gt;: "We appreciate your interest in optimization, but that’s internal information."&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trying to improve efficiency?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"You want to refactor that? Sounds like you're trying to scam us into paying your company more."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Coding standards inconsistently applied?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Bob doesn't need to follow them -- his team is knows what they are doing."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Timesheets?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Contractors: Mandatory. And increase granularity to 3-hour maximums.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Employees: No need.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Team building?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We'd love to invite you, but we can't."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Promotions?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Senior roles are for employees only. Talk to your manager. Forgot to mention, Bob, the junior developer you onboarded three years ago now would be your tech lead. You did all the architectural work, so please help him."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Stock options? Bonuses?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Your performance is outstanding even without them."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Parallel with Agentic Teams
&lt;/h2&gt;

&lt;p&gt;This is what the opportunity to get a "remote agentic team" often looked like from my perspective.&lt;/p&gt;

&lt;p&gt;So I have questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Is it really that different to grant build pipeline access to a third-party user versus a third-party agent?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Could AI providers to raise prices just as an outsourcing company would?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When an LLM suggests refactoring or using a new tool, is that a scam or an improvement?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is &lt;code&gt;AGENTS.md&lt;/code&gt; fundamentally different from the project guidelines that were supposed to be written anyway?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the bigger question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If a company couldn't effectively utilize a team it could talk to in person, will it be able to utilize a team it can't?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Closing Thought
&lt;/h2&gt;

&lt;p&gt;Outsourcing wasn't just about cost arbitrage. It was about elasticity -- about converting capital into capacity.&lt;/p&gt;

&lt;p&gt;AI agents promise the same thing.&lt;/p&gt;

&lt;p&gt;The real question is not whether agents will work. It's whether organizations that struggled with human third parties are structurally ready for non-human ones.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>career</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Localization Keys vs. Direct Text Keys</title>
      <dc:creator>spyke</dc:creator>
      <pubDate>Mon, 03 Mar 2025 21:45:35 +0000</pubDate>
      <link>https://dev.to/spyke/localization-keys-vs-direct-text-keys-5i</link>
      <guid>https://dev.to/spyke/localization-keys-vs-direct-text-keys-5i</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This is a &lt;strong&gt;Deep Research&lt;/strong&gt; report on the topic of the advantages and disadvantages of using keys vs. direct English text for localization, considering your priorities of maintainability and ease of development in the context of i18next localization in a TypeScript + React.js project.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Internationalizing a React application involves deciding how to reference UI strings in code. Two common approaches are &lt;strong&gt;using abstract localization keys&lt;/strong&gt; (e.g. &lt;code&gt;UNT:BannerBody_NACS_adapter_required&lt;/code&gt;) and &lt;strong&gt;using the actual English text as the key&lt;/strong&gt; (e.g. &lt;code&gt;"You need to use a charging adapter on this route."&lt;/code&gt;). Each approach has its own advantages and disadvantages. This guide compares them across several factors — maintainability, scalability, ease of development, translation workflow, developer readability, cross-language consistency, and handling of plurals/dynamic content — and provides recommendations for using i18next in a TypeScript + React project. We’ll also cover best practices to keep translations maintainable and developer-friendly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approaches to Referencing Translation Strings
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Key-Based Localization (Abstract Keys)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; In this approach, strings in the code are referenced by a &lt;strong&gt;key&lt;/strong&gt; that acts as an identifier. The key is typically a short code or path (often in English or an encoded form) that maps to the actual text in a translation file. For example, &lt;code&gt;t('errors.networkTimeout')&lt;/code&gt; might look up a JSON entry like &lt;code&gt;"errors": { "networkTimeout": "The request timed out." }&lt;/code&gt;. The key itself is not shown to users; it’s only used to retrieve the correct localized text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros (Key-Based):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Maintainable &amp;amp; Flexible Content:&lt;/em&gt; You can change the actual displayed text without altering application code – only the translation files need updates (&lt;a href="https://github.com/nodejs/i18n/issues/50#:~:text=Concept%20is%20that%20you%20define,it%20in%20the%20translation%20file" rel="noopener noreferrer"&gt;Key based i18n vs default language i18n · Issue #50 · nodejs/i18n · GitHub&lt;/a&gt;). This means minor copy changes or tweaks in English (or any language) don’t require code deployments. The key stays the same (stable), so other languages’ translations remain linked to it even if the English phrasing changes (you’d simply update the English translation for that key) (&lt;a href="https://github.com/nodejs/i18n/issues/50#:~:text=Concept%20is%20that%20you%20define,it%20in%20the%20translation%20file" rel="noopener noreferrer"&gt;Key based i18n vs default language i18n · Issue #50 · nodejs/i18n · GitHub&lt;/a&gt;). This stability is great for long-term maintainability when text may evolve.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Structured Organization:&lt;/em&gt; Keys can be organized hierarchically (e.g. grouped by screen or feature), improving manageability in large apps (&lt;a href="https://poeditor.com/blog/advantages-of-using-translation-keys/#:~:text=By%20assigning%20unique%20identifiers%20to,text%2C%20simplifying%20tasks%20such%20as" rel="noopener noreferrer"&gt;8 Advantages of using translation keys in your localization files - POEditor Blog&lt;/a&gt;) (&lt;a href="https://poeditor.com/blog/advantages-of-using-translation-keys/#:~:text=One%20of%20the%20most%20compelling,used%20consistently%20across%20various%20contexts" rel="noopener noreferrer"&gt;8 Advantages of using translation keys in your localization files - POEditor Blog&lt;/a&gt;). For instance, keys might be prefixed with the page or component name (&lt;code&gt;"Checkout.PaymentError.CardDeclined"&lt;/code&gt;), making it clear where they are used. This organization helps keep hundreds or thousands of strings scalable and easy to find.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Context &amp;amp; Uniqueness:&lt;/em&gt; Well-chosen keys can encode context or meaning. This avoids collisions and ambiguity. For example, you might use separate keys like &lt;code&gt;button.openFile&lt;/code&gt; vs &lt;code&gt;status.fileOpen&lt;/code&gt; to distinguish the word “Open” as an action vs. a state (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=I%20like%20your%20second%20approach,and" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;). Dedicated identifiers provide extra context about where/how the text is used (&lt;a href="https://www.reddit.com/r/godot/comments/1hxjzs2/using_english_text_as_the_translation_csv_files/#:~:text=%E2%80%A2%20%E2%80%A2%20Edited" rel="noopener noreferrer"&gt;Using english text as the translation csv file's keys. Good or terrible idea? : r/godot&lt;/a&gt;), helping translators choose the right translation. Keys are unique IDs, so the same English word in different contexts can have different translations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Pluralization &amp;amp; Dynamic Content:&lt;/em&gt; Key-based catalogs integrate naturally with i18n features for plurals and interpolation. You define base keys and variant forms in translation files (e.g. &lt;code&gt;"itemCount_one": "1 item", "itemCount_other": "{{count}} items"&lt;/code&gt;), and i18next will select the correct form when you call &lt;code&gt;t('itemCount', { count })&lt;/code&gt; (&lt;a href="https://www.i18next.com/translation-function/plurals#:~:text=Languages%20with%20multiple%20plurals,key_one" rel="noopener noreferrer"&gt;Plurals - i18next documentation&lt;/a&gt;). This keeps plural logic in the localization system. Similarly, keys with placeholders (e.g. &lt;code&gt;"welcomeMessage": "Hello, {{name}}!"&lt;/code&gt;) allow i18next to handle dynamic insertion. In code you just use the key with variables (no need to build strings manually).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Type Safety (with TypeScript):&lt;/em&gt; It’s possible to generate or maintain a TypeScript type for all valid keys, so the &lt;code&gt;t(…)&lt;/code&gt; function only accepts known keys. This prevents typos or missing key errors at compile time. Many teams auto-generate TypeScript definitions from the JSON translation files. This benefit is more feasible with short keys than with long sentence keys.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons (Key-Based):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Development Overhead:&lt;/em&gt; Developers must &lt;strong&gt;define and maintain keys&lt;/strong&gt;. Adding a new UI message means coming up with a new key and writing the English text in a translation file, effectively writing the message twice (once as key, once as value) (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=We%27ve%20been%20using%20abstract%20placeholders,to%20think%20about%20naming%20placeholders" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;). Without tooling, this extra step can slow down development and invite mistakes (e.g. forgetting to add the key to the translations). It’s more work upfront compared to just writing the string directly in the component.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Reduced Readability:&lt;/em&gt; Code is less self-explanatory. A key like &lt;code&gt;UNT:BannerBody_NACS_adapter_required&lt;/code&gt; or even &lt;code&gt;errors.networkTimeout&lt;/code&gt; doesn’t immediately tell a developer or designer what text the user will see. Developers may need to look up the key in the locale file to know the actual message. Poorly named keys make this worse (e.g. a generic &lt;code&gt;error_42&lt;/code&gt; gives no clue). This context switching can hinder quick understanding of the UI logic (&lt;a href="https://www.reddit.com/r/vuejs/comments/176tyvz/i18n_do_you_prefer_using_one_big_translation/#:~:text=It%20simply%20makes%20the%20codebase,easier%20to%20understand%20and%20read" rel="noopener noreferrer"&gt;i18n: Do you prefer using one big translation dictionary for your whole app, or sprinkle translation strings inside your components? : r/vuejs&lt;/a&gt;). However, using &lt;strong&gt;descriptive keys&lt;/strong&gt; (containing hints of the content) can mitigate this. For example, a key &lt;code&gt;login.error.invalidPassword&lt;/code&gt; is somewhat readable, and some teams use “natural language” phrases as part of keys for clarity (&lt;a href="https://www.reddit.com/r/vuejs/comments/176tyvz/i18n_do_you_prefer_using_one_big_translation/#:~:text=Scan%20or%20type%20in%20,this%20page%20is%20location%20above" rel="noopener noreferrer"&gt;i18n: Do you prefer using one big translation dictionary for your whole app, or sprinkle translation strings inside your components? : r/vuejs&lt;/a&gt;) (&lt;a href="https://www.reddit.com/r/vuejs/comments/176tyvz/i18n_do_you_prefer_using_one_big_translation/#:~:text=It%20simply%20makes%20the%20codebase,easier%20to%20understand%20and%20read" rel="noopener noreferrer"&gt;i18n: Do you prefer using one big translation dictionary for your whole app, or sprinkle translation strings inside your components? : r/vuejs&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Synchronization with Design/Copy Changes:&lt;/em&gt; If keys are very abstract or developer-oriented, it might be harder for non-developers (like PMs or copywriters) to see where text lives or to ensure updates are applied in all places. A robust process or tools (like a translation management system) is needed to keep keys and actual content in sync.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Potential for Outdated Keys:&lt;/em&gt; Because keys are meant to be stable, there’s a risk that the English translation for a key changes over time but the key name stays the same. For example, the key &lt;code&gt;label.submit&lt;/code&gt; might have originally been “Submit Order” and later changed to “Place Order” in English. The code still uses &lt;code&gt;label.submit&lt;/code&gt;, which could confuse developers if they assume the key matches the old wording. This &lt;strong&gt;outdated key&lt;/strong&gt; issue is mostly a naming concern — using clear, &lt;strong&gt;semantic keys&lt;/strong&gt; (like &lt;code&gt;action.placeOrder&lt;/code&gt; instead of the generic &lt;code&gt;label.submit&lt;/code&gt;) can help, as can documentation or even renaming keys when necessary (though renaming keys requires updating all translations).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Using English Text as Keys (Natural Keys)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; This approach uses the actual English string (or a close variant of it) as the key in code. In other words, the default language text itself serves as the identifier. For example, a developer writes &lt;code&gt;t("You need to use a charging adapter on this route.")&lt;/code&gt; directly. With i18next, this typically relies on treating the key literally as the English source string and using it as a fallback if no translation is provided (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=1,placeholder%20as%20the%20translated%20text" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;). The translation files might even have the English sentence as both the key and the value for the base language.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros (Natural Keys):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Immediate Readability &amp;amp; Less Guesswork:&lt;/em&gt; The code contains human-readable sentences, so any developer browsing the JSX/TSX can understand what message will appear in the UI (&lt;a href="https://react.i18next.com/legacy-v9/step-by-step-guide#3-sidequest-natural-vs-keybased-catalog#:~:text=This%20was%20possible%20by%20setting,i18n.init" rel="noopener noreferrer"&gt;Step by step guide (v9) | react-i18next documentation&lt;/a&gt;). This makes the interface logic clear without opening a separate file. It’s also easier to search the codebase for a phrase you see in the app and find where it’s used (since the same text is in the code) (&lt;a href="https://www.reddit.com/r/vuejs/comments/176tyvz/i18n_do_you_prefer_using_one_big_translation/#:~:text=It%20simply%20makes%20the%20codebase,easier%20to%20understand%20and%20read" rel="noopener noreferrer"&gt;i18n: Do you prefer using one big translation dictionary for your whole app, or sprinkle translation strings inside your components? : r/vuejs&lt;/a&gt;). Debugging is more straightforward — seeing &lt;code&gt;t("File not found.")&lt;/code&gt; in code is obvious, whereas &lt;code&gt;t("error.fileNotFound")&lt;/code&gt; requires an extra lookup.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Faster Development Workflow:&lt;/em&gt; Using English strings as keys means developers &lt;strong&gt;don’t have to create separate key names&lt;/strong&gt; or initially populate translation files for the default language. You “write it once” – just put the English text in the &lt;code&gt;t()&lt;/code&gt; call, and you instantly have a working UI in English (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=We%27ve%20been%20using%20abstract%20placeholders,to%20think%20about%20naming%20placeholders" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;). This minimizes the impact of i18n on development, keeping things as simple as writing normal strings (which is a design goal of frameworks like gettext (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;)). In practice, this can speed up prototyping and reduce the cognitive load of naming things. It also avoids the scenario of seeing empty or placeholder text in the UI before translations are added – the English text serves as a placeholder that is user-readable from the start (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=1,placeholder%20as%20the%20translated%20text" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Better Context for Translators:&lt;/em&gt; If you use a system like gettext or i18next with natural keys, translators effectively see the full English sentence as the source text to translate. This provides more context than a cryptic key would (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=1,placeholder%20as%20the%20translated%20text" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;). Translators can often produce more accurate translations when they see a complete English phrase (especially if the phrase itself is self-explanatory). While key-based workflows typically also show the English source (as a separate field), the natural-key approach &lt;em&gt;guarantees&lt;/em&gt; that the key is exactly the English source. There’s no risk of a missing or outdated developer comment – the sentence itself carries meaning. This can simplify the translation workflow (fewer lookup steps to see context).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;No English “Translation” Needed:&lt;/em&gt; The default language (English) is inherently covered because the app will display the key string if no other translation is loaded (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=1,placeholder%20as%20the%20translated%20text" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;). You might not even need an English JSON file for i18next if you configure it to fall back to the key. This reduces duplication of having the same English text in both code and an English resource file. (However, for consistency, many teams still maintain an English translation file to use in the translation workflow, as discussed later.)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons (Natural Keys):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Maintenance on Content Changes:&lt;/em&gt; &lt;strong&gt;Changing the English text means changing the key&lt;/strong&gt;, which requires code changes and re-translating into all other languages (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=I%20have%20seen%20various%20recommendations,Or%20don%27t%20you" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;) (&lt;a href="https://www.reddit.com/r/godot/comments/1hxjzs2/using_english_text_as_the_translation_csv_files/#:~:text=What%20if%20two%20places%20have,context%20and%20need%20different%20translations" rel="noopener noreferrer"&gt;Using english text as the translation csv file's keys. Good or terrible idea? : r/godot&lt;/a&gt;). This is the biggest drawback. For example, if the key/text &lt;code&gt;"You need to use a charging adapter on this route."&lt;/code&gt; needs to be rephrased to &lt;code&gt;"A charging adapter is required for this route."&lt;/code&gt;, you can’t simply edit a translation file – you must update every place that calls &lt;code&gt;t("You need to use a charging adapter on this route.")&lt;/code&gt; to use the new string (and update every translation memory). Essentially, the old key is deprecated and a new key is introduced. All existing translations for the old sentence either become invalid or must be copied over if still applicable. This can be &lt;strong&gt;cumbersome and error-prone&lt;/strong&gt; for large projects (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=I%20have%20seen%20various%20recommendations,Or%20don%27t%20you" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;). Some workflows mitigate this by treating the old phrase as an identifier (never changing it in code) and only updating the displayed text via translation files (i.e. an "override" in the English translation) (&lt;a href="https://github.com/nodejs/i18n/issues/50#:~:text=Concept%20is%20that%20you%20define,it%20in%20the%20translation%20file" rel="noopener noreferrer"&gt;Key based i18n vs default language i18n · Issue #50 · nodejs/i18n · GitHub&lt;/a&gt;). While that preserves code stability, it means the code’s string no longer matches the actual UI text, potentially causing confusion.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Context Collisions:&lt;/em&gt; Using full sentences as keys can backfire if the same English sentence appears in different contexts requiring different translations (&lt;a href="https://www.reddit.com/r/godot/comments/1hxjzs2/using_english_text_as_the_translation_csv_files/#:~:text=What%20if%20two%20places%20have,context%20and%20need%20different%20translations" rel="noopener noreferrer"&gt;Using english text as the translation csv file's keys. Good or terrible idea? : r/godot&lt;/a&gt;). For instance, the word “Archive” could be a noun in one place and a verb in another, and English might use the same word for both. If you used &lt;code&gt;"Archive"&lt;/code&gt; as the key in two contexts, i18n will treat them as one entry – both instances will get the same translation in other languages, which may be incorrect. With abstract keys, you would have defined two distinct keys (like &lt;code&gt;folder.archive.label&lt;/code&gt; vs &lt;code&gt;action.archive&lt;/code&gt;) to distinguish them. Natural keys make it harder to manage these nuances &lt;strong&gt;unless you deliberately alter the English phrasing to differentiate keys&lt;/strong&gt;, which is not ideal. This can lead to inconsistent or wrong translations if not carefully handled. It’s safer when your phrases are long/unique, but common short phrases can collide.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Loss of Systematic Organization:&lt;/em&gt; While you can still organize natural keys by namespace or comments, you lose some inherent structure that coded keys offer. The translation files might end up as a flat list of English sentences. It can be harder to see at a glance which part of the app a string belongs to just from the key. Additional metadata or naming conventions (like prefixing keys with a context manually) might be needed to maintain order. Without that, a large project using plain sentences as keys can become messy, with duplicates or very similar sentences scattered around.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Pluralization &amp;amp; Dynamic Content Challenges:&lt;/em&gt; If you rely on the English sentence as the key, handling plural forms and injected variables requires care. You might end up writing multiple full-sentence keys for singular and plural versions, which can be repetitive. For example, you’d have one key &lt;code&gt;"You have one new message."&lt;/code&gt; and another key &lt;code&gt;"You have {{count}} new messages."&lt;/code&gt; as separate entries, since i18next’s automatic pluralization rules expect a base key to modify (they work more naturally with abstract base keys like &lt;code&gt;inbox.count_one&lt;/code&gt;/&lt;code&gt;inbox.count_other&lt;/code&gt;). It’s possible to use ICU syntax or similar with natural keys, but at that point you’re almost treating the sentence like a mini-format rather than a literal key. In short, &lt;strong&gt;automatic plural handling is less straightforward&lt;/strong&gt; with natural keys – you might end up managing plural variants manually, whereas with key-based approach you’d typically use i18next’s built-in plural keys or context for gender, etc. Similarly, for dynamic content, you must ensure the key is written as a template string (with placeholders) and hope it’s not misinterpreted. (Usually i18next will still replace &lt;code&gt;{{placeholder}}&lt;/code&gt; in the fallback key text, so it does work, but you have to include those placeholders in the key exactly.) This is doable, but some teams find it cleaner to use a concise key and keep the full sentence (with placeholders) in the translations instead.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Potential Performance/Size Concerns:&lt;/em&gt; Each key in your translation resources might be a lengthy sentence. This can marginally increase the size of your translation files and memory usage, since the key is duplicated as the English default. In practice this is usually negligible and not a huge factor, but it’s less efficient than short keys (especially if a sentence is repeated identically in many places – you’d have the same long key string stored multiple times unless you refactor to reuse it). Given the question’s focus on maintainability over performance, this is likely a minor point, but worth noting for very resource-constrained contexts.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Comparison by Key Factors
&lt;/h2&gt;

&lt;p&gt;Below is a factor-by-factor comparison summarizing how each approach fares:&lt;/p&gt;

&lt;h3&gt;
  
  
  Maintainability
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Key-Based:&lt;/strong&gt; High maintainability for evolving content. Since the &lt;strong&gt;key is decoupled from the actual phrase&lt;/strong&gt;, you can adjust messaging without touching code. This makes updates and A/B testing of text much easier – only the localization files change. It also means you won’t force re-translation of other languages unless the meaning truly changed. However, maintainability depends on disciplined key naming. If keys are poorly named or if the English text changes meaning but you forget to notify translators (because the key didn’t change), you can end up with outdated translations. Overall, using stable keys acts as a layer of indirection that supports maintainability in the long run (&lt;a href="https://github.com/nodejs/i18n/issues/50#:~:text=Concept%20is%20that%20you%20define,it%20in%20the%20translation%20file" rel="noopener noreferrer"&gt;Key based i18n vs default language i18n · Issue #50 · nodejs/i18n · GitHub&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;English Text as Key:&lt;/strong&gt; More brittle for maintenance. The English text is directly wired into code, so &lt;strong&gt;any text change is a code change&lt;/strong&gt; that cascades to all translations (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=I%20have%20seen%20various%20recommendations,Or%20don%27t%20you" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;). This tight coupling can hinder quick content tweaks – minor rephrasing becomes a development task and requires updating every other language’s entry (or doing a find-and-replace on keys). In a fast-paced project, this can accumulate overhead or discourage refining copy. Without careful management, you risk &lt;strong&gt;stale keys&lt;/strong&gt; (if you avoid changing the key and thus display a different string than the key suggests) or inconsistent updates (if one instance of a sentence was changed but another identical one wasn’t, you might inadvertently fork what should be a single translation entry). Maintenance is simpler only as long as the English never needs to change. For a static content app this might be fine, but for most projects text does evolve. As one developer cautioned about using English strings as IDs: &lt;em&gt;“it’s just a matter of time until you hit a corner case which will require you to redo 2/3 of the project”&lt;/em&gt; (&lt;a href="https://www.reddit.com/r/godot/comments/1hxjzs2/using_english_text_as_the_translation_csv_files/#:~:text=I%20have%20used%20that%20in,redo%202%2F3%20of%20the%20project" rel="noopener noreferrer"&gt;Using english text as the translation csv file's keys. Good or terrible idea? : r/godot&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Key-based wins on maintainability for apps where text is updated or refined frequently. The indirection adds initial work but saves effort in the long run when copy changes. Natural keys can be maintainable in scenarios where messages are essentially final or very unlikely to change, but that’s rare. Even then, you should be prepared for the eventual maintenance burden if a change is needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scalability
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Key-Based:&lt;/strong&gt; Designed for scalability. Large applications with many pages and languages benefit from the structured approach of keys. You can categorize and partition translation files by feature or module (e.g. using i18next &lt;strong&gt;namespaces&lt;/strong&gt; per page or section). This modularization means as the app grows, translations remain organized rather than one gigantic flat file. Keys also promote &lt;strong&gt;reusability&lt;/strong&gt;: if the same concept appears in multiple places, you can use the same key everywhere and translate it once, ensuring consistency and saving translator effort (&lt;a href="https://poeditor.com/blog/advantages-of-using-translation-keys/#:~:text=One%20of%20the%20most%20compelling,used%20consistently%20across%20various%20contexts" rel="noopener noreferrer"&gt;8 Advantages of using translation keys in your localization files - POEditor Blog&lt;/a&gt;). Version control of translations is clearer with keys too – diffs show which keys changed, and you can track changes in meaning. Overall, key-based catalogs handle growth in both content and number of languages gracefully (&lt;a href="https://poeditor.com/blog/advantages-of-using-translation-keys/#:~:text=Organization%20and%20maintainability" rel="noopener noreferrer"&gt;8 Advantages of using translation keys in your localization files - POEditor Blog&lt;/a&gt;) (&lt;a href="https://poeditor.com/blog/advantages-of-using-translation-keys/#:~:text=One%20of%20the%20most%20compelling,used%20consistently%20across%20various%20contexts" rel="noopener noreferrer"&gt;8 Advantages of using translation keys in your localization files - POEditor Blog&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;English Text as Key:&lt;/strong&gt; Simpler in small scale, but can get unwieldy as things grow. In a small app, it’s straightforward – each string is just itself. But as the number of strings increases, managing them without a key hierarchy might become difficult. You might end up with duplicates (the same sentence written slightly differently in two places) which could have been a single reusable key in a key-based approach. Also, if multiple developers are adding strings, slight variations in phrasing could slip in, making translations inconsistent or redundant. There is also a potential performance consideration at scale: if each key is a long sentence, your translation lookups and memory usage might be marginally heavier (though in practice i18n libraries handle thousands of keys fine). Another scalability issue is maintaining consistency – with natural keys, ensuring that common phrases are translated uniformly across the app relies on humans noticing the repetition, whereas keys could enforce reuse. &lt;strong&gt;In translation workflow terms&lt;/strong&gt;, if you have a lot of strings, having unique identifiers (keys) might help translators and tools manage the content (for example, identifying when one English phrase is just a duplicate entry vs a new context).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Key-based is more scalable and easier to keep organized as projects grow in size and languages. It’s the preferred approach in large-scale applications and by most localization platforms (&lt;a href="https://poeditor.com/blog/advantages-of-using-translation-keys/#:~:text=Organization%20and%20maintainability" rel="noopener noreferrer"&gt;8 Advantages of using translation keys in your localization files - POEditor Blog&lt;/a&gt;). Natural keys can work for smaller projects or those with very limited dynamic text, but risk becoming chaotic at scale without strict conventions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ease of Development
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Key-Based:&lt;/strong&gt; Initially, a bit more work for the developer. You have to decide on a key name, add it to the translations, and then call &lt;code&gt;t('your.key.name')&lt;/code&gt;. This two-step process (write key in code, write text in file) can slow down iteration (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=We%27ve%20been%20using%20abstract%20placeholders,to%20think%20about%20naming%20placeholders" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;). However, modern workflows mitigate this: for example, i18next lets you pass a default value inline (&lt;code&gt;t('error.networkTimeout', 'The request timed out.')&lt;/code&gt;) which can auto-fill missing translations during development (&lt;a href="https://www.i18next.com/translation-function/essentials#:~:text=You%20can%20pass%20in%20a,be%20found%20in%20translations%20like" rel="noopener noreferrer"&gt;Essentials | i18next documentation&lt;/a&gt;). There are also tools that can &lt;strong&gt;extract keys and strings&lt;/strong&gt; from code to generate translation files, or even live-reload missing keys. Once these tools or patterns are in place, adding new strings becomes more seamless. Another aspect is cognitive: thinking of a short key name that conveys the purpose is an extra mental step for the developer. Some find this trivial, others find it disruptive when writing a lot of UI text. On the positive side, key-based approach forces developers to think about reuse and context, which can be good for consistency. And with TypeScript, if you have types for keys, the IDE auto-completion can actually aid development by suggesting available keys.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;English as Key:&lt;/strong&gt; Extremely straightforward for developers, especially at the start. You simply write the message as you want the user to see it (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=We%27ve%20been%20using%20abstract%20placeholders,to%20think%20about%20naming%20placeholders" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;). This means you get immediate UI text without worrying about missing translation entries or naming. It’s very WYSIWYG: what you put in &lt;code&gt;t("...")&lt;/code&gt; is what shows up (at least for English). This often results in &lt;strong&gt;faster prototyping&lt;/strong&gt;. As one developer put it, using English strings directly gave &lt;em&gt;“meaningful output from the start and [you] don't have to think about naming placeholders”&lt;/em&gt;, resulting in &lt;em&gt;“less work for the developers.”&lt;/em&gt; (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=We%27ve%20been%20using%20abstract%20placeholders,to%20think%20about%20naming%20placeholders" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;). You also avoid the scenario of forgetting to add a translation and seeing a raw key or no text – here, the fallback is the English string which is perfectly readable. This approach aligns well with agile development where you might not have all copy finalized but need something on screen now. The drawback is that down the line, if those strings need to change or be referenced elsewhere, the lack of an abstract key can slow you (as discussed in maintainability). But from a pure &lt;em&gt;coding&lt;/em&gt; perspective, many find this approach easier and more intuitive.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; For development speed and simplicity, using English strings as keys has an edge, especially early in a project or for less experienced i18n developers (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=We%27ve%20been%20using%20abstract%20placeholders,to%20think%20about%20naming%20placeholders" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;). Key-based can be nearly as smooth with good tooling (like default values, extraction scripts, etc.), but it has a learning curve and initial overhead. If developer productivity and minimizing steps is the priority (and you’re willing to accept some technical debt in translations), the natural key approach is attractive. Just weigh this against future maintenance costs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Translation Workflow
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Key-Based:&lt;/strong&gt; This is the traditional workflow expected by most localization teams and tools. Developers maintain an English resource (e.g. an &lt;code&gt;en.json&lt;/code&gt; file) where each key has an English string. Translators use a translation management system (TMS) or files where they see something like: &lt;code&gt;errors.networkTimeout ⇒ "The request timed out."&lt;/code&gt; as the source and then provide, say, French &lt;code&gt;"La requête a expiré."&lt;/code&gt; for that key. &lt;strong&gt;Context for translators&lt;/strong&gt; can be provided via the key name (if it’s descriptive) and additional comments or screenshots. One advantage is that keys can be accompanied by developer comments explaining usage (most TMS support a comments field per key). Translators typically &lt;strong&gt;see the English (or source language) text alongside the key&lt;/strong&gt;, so they know what to translate (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=3" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;). The key itself might not mean much to them, but it doesn’t have to — it’s just an identifier. As long as the English (source) string and any notes are clear, translation is straightforward. Another advantage: if the English text changes (but the key stays same for semantic reasons), the system can flag that the translations for that key might need updating (since the "source" changed). Many TMS support versioning or a “re-translate” flag when source text updates. This approach scales to many languages easily and avoids duplication in translation work because each key is translated once and reused. The downside is that without a good tool, a translator working from raw files might see a key like &lt;code&gt;BTN_CONFIRM&lt;/code&gt; and not know what it means without context. But in a robust workflow, context is managed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;English as Key:&lt;/strong&gt; This can simplify or complicate the translator’s job depending on the tooling. In a basic scenario, you might not have a separate English source file at all – the English text is the key. If using something like gettext, the &lt;code&gt;.pot&lt;/code&gt; file (catalog of source strings) is basically a list of English phrases. Translators translate those into target languages. This is a very direct workflow: the English text &lt;em&gt;is&lt;/em&gt; the source text. Translators definitely have context because they see the full string they need to translate (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=1,placeholder%20as%20the%20translated%20text" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;). In our example, the translator would directly see “You need to use a charging adapter on this route.” and provide a translation. This is efficient and avoids the need for an English key description. However, there are a few pitfalls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the same English string is used in two different places for different purposes, a translator might only see one entry and provide a single translation, which could be wrong for one of the contexts (because the system doesn’t know they were meant to be distinct). Gettext mitigates this with context flags (msgctxt) if needed, but that requires developers to mark contexts. Without such measures, the translator might not even know that one English phrase actually appears twice in different contexts with potentially different meanings.&lt;/li&gt;
&lt;li&gt;If you change an English sentence slightly, you’ll generate a new key and the translator will see it as a new string to translate, with the old one potentially removed. Unless the translation memory matches it, they might have to translate a very similar sentence again. In contrast, a stable key would have shown the English change and possibly allowed the translator to update the existing translation with minimal effort.&lt;/li&gt;
&lt;li&gt;Some translation platforms prefer having stable keys as identifiers. If you integrate with a service that expects keys, using the full sentence as the key is still possible but sometimes the tooling around comments, screenshots, etc., might assume keys are not huge text. Generally, though, gettext-based workflows and many modern TMS do support using the source string as key (often called “source string-based localization”).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;In summary, translators will find natural keys &lt;strong&gt;very straightforward for understanding context&lt;/strong&gt;, since the source is explicit (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=1,placeholder%20as%20the%20translated%20text" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;). But project managers and engineers managing translations might find it harder to track changes and maintain consistency without the abstraction layer. Also, coordinating large changes (like renaming a term across the app) is easier with keys (one key, many languages to update) versus searching through many strings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Both approaches can fit into standard translation workflows, but key-based is generally more robust for large teams and external translation services. English-as-key can be perfectly fine when using tools designed for gettext or similar paradigms – it’s essentially what many open-source projects do. The key is to ensure translators have context for each string. If using English keys, you might rely less on separate comments since the string is there, but you should still provide notes if something isn’t obvious or if a placeholder like &lt;code&gt;{count}&lt;/code&gt; appears. If using coded keys, providing the English source string and context in the TMS is essential so translators aren’t guessing (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=3" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Readability for Developers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Key-Based:&lt;/strong&gt; Requires developers (and anyone reading the code) to mentally map keys to actual content. If keys are descriptive (e.g. &lt;code&gt;user.profile.saveSuccess&lt;/code&gt;), they at least hint at meaning, but they might still be somewhat abstract. New developers might have a harder time understanding the UI text without running the app or checking the translation files. This can slow down code reviews or debugging. That said, consistent naming conventions help a lot – if your team agrees on how keys are structured, a developer can often infer the message. For example, seeing &lt;code&gt;error.payment.declined&lt;/code&gt; in code clearly is an error message about payment declined, which is almost as good as seeing the full sentence. Some IDE integrations or simple editor tricks (like searching the JSON) can also bridge the gap. In terms of &lt;strong&gt;code aesthetics&lt;/strong&gt;, keys keep the code compact and language-agnostic, which some prefer (the code isn’t cluttered with long text strings). But the main drawback is the cognitive load: it's an indirection that can make the code less immediately readable (&lt;a href="https://www.reddit.com/r/vuejs/comments/176tyvz/i18n_do_you_prefer_using_one_big_translation/#:~:text=It%20simply%20makes%20the%20codebase,easier%20to%20understand%20and%20read" rel="noopener noreferrer"&gt;i18n: Do you prefer using one big translation dictionary for your whole app, or sprinkle translation strings inside your components? : r/vuejs&lt;/a&gt;), especially if keys are not well-chosen.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;English as Key:&lt;/strong&gt; Very high readability in code – essentially self-documenting. When you read &lt;code&gt;t("Delete user profile")&lt;/code&gt;, you know exactly what the UI is conveying. This can make development and code reviews quicker in terms of understanding intent. It also reduces context switching: you don’t need to open another file or rely on memory for what a key means (&lt;a href="https://www.reddit.com/r/vuejs/comments/176tyvz/i18n_do_you_prefer_using_one_big_translation/#:~:text=It%27s%20hard%20to%20jump%20between,and%20with%20less%20cyclic%20complexity" rel="noopener noreferrer"&gt;i18n: Do you prefer using one big translation dictionary for your whole app, or sprinkle translation strings inside your components? : r/vuejs&lt;/a&gt;). Additionally, searching the repository for a phrase you see in the UI will directly lead you to where it's implemented, which is great for debugging (&lt;a href="https://www.reddit.com/r/vuejs/comments/176tyvz/i18n_do_you_prefer_using_one_big_translation/#:~:text=and%20understand%20what%20the%20code,and%20with%20less%20cyclic%20complexity" rel="noopener noreferrer"&gt;i18n: Do you prefer using one big translation dictionary for your whole app, or sprinkle translation strings inside your components? : r/vuejs&lt;/a&gt;). On the flip side, the presence of large chunks of text in the code can be a bit distracting, and if you have multi-sentence strings, it might affect code formatting or line length. But generally, developers value clarity, and there’s not much ambiguity when the actual sentence is in the code. The only time this can become confusing is if, as mentioned, the code’s text is no longer the real text (if you’ve overridden English in the translation file or something). In that case, the code might lie about what’s shown, which is arguably worse than an abstract key – so one must avoid that scenario or comment it clearly.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Using English (natural language) as keys provides better readability and transparency in the codebase (&lt;a href="https://react.i18next.com/legacy-v9/step-by-step-guide#3-sidequest-natural-vs-keybased-catalog#:~:text=This%20was%20possible%20by%20setting,i18n.init" rel="noopener noreferrer"&gt;Step by step guide (v9) | react-i18next documentation&lt;/a&gt;) (&lt;a href="https://www.reddit.com/r/vuejs/comments/176tyvz/i18n_do_you_prefer_using_one_big_translation/#:~:text=It%20simply%20makes%20the%20codebase,easier%20to%20understand%20and%20read" rel="noopener noreferrer"&gt;i18n: Do you prefer using one big translation dictionary for your whole app, or sprinkle translation strings inside your components? : r/vuejs&lt;/a&gt;). It lowers the barrier for any contributor to understand the interface text. Key-based references make the code a bit more opaque, though good naming conventions can alleviate this. If developer readability is a top concern (e.g., in open source projects or teams where developers themselves change text often), natural keys shine. If keys are used, invest in making them as semantic as possible (some teams even include part of the phrase in the key for readability (&lt;a href="https://www.reddit.com/r/vuejs/comments/176tyvz/i18n_do_you_prefer_using_one_big_translation/#:~:text=Scan%20or%20type%20in%20,this%20page%20is%20location%20above" rel="noopener noreferrer"&gt;i18n: Do you prefer using one big translation dictionary for your whole app, or sprinkle translation strings inside your components? : r/vuejs&lt;/a&gt;) (&lt;a href="https://www.reddit.com/r/vuejs/comments/176tyvz/i18n_do_you_prefer_using_one_big_translation/#:~:text=It%20simply%20makes%20the%20codebase,easier%20to%20understand%20and%20read" rel="noopener noreferrer"&gt;i18n: Do you prefer using one big translation dictionary for your whole app, or sprinkle translation strings inside your components? : r/vuejs&lt;/a&gt;), as a compromise).&lt;/p&gt;

&lt;h3&gt;
  
  
  Consistency Across Languages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Key-Based:&lt;/strong&gt; This approach inherently treats each key as a single concept that should be consistently translated in each language. It &lt;strong&gt;promotes consistency&lt;/strong&gt; because all languages are keyed off the same identifier. If you use one key in multiple places, all languages will use their translation of that key, keeping the messaging uniform. It also allows enforcing consistency: if two English strings should be translated the same in French, you can deliberately use one key for both so that translators only provide one translation. Additionally, keys with contextual info ensure that translators know the intent (so they don’t mistakenly translate two identical English words the same when in context they should differ). When maintaining multiple languages, key-based setups make it easier to spot when one language is out-of-date (e.g., the English text changed but French still has the old translation – the key is the same, but you can mark the French entry as needing update). Another consistency benefit is the ability to do things like “pseudo-localization” or automated checks – keys make it clear which strings correspond across languages. Overall, a structured key system acts as a backbone aligning all languages to the same set of messages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;English as Key:&lt;/strong&gt; In terms of consistency, using the English string as the pivot can work, but it has some risks. If the English phrasing is the single source of truth, other languages will attempt to mirror that meaning. As long as that holds, translations will be consistent with English. However, if English phrasing is changed (creating a new key), there’s a chance not all languages update simultaneously, leading to a period where English has one message and others still reflect the old message (since the link was broken by changing the key). Also, if two languages need slightly different nuance, you don’t have a straightforward way to handle that via keys – though that’s more a translation issue than a key issue (usually handled via separate keys or context). Another subtle consistency issue: two different English strings that mean the same thing (synonyms) will produce two separate keys that could be translated differently in some language. For example, if one part of the app says "Close" and another says "Exit" (English synonyms) and you used both as keys, a translator might not realize they should use the same word in their language for both. Key-based approach could have enforced a single term by using one key for both or by at least making the relationship obvious. Essentially, natural keys might make it harder to detect and enforce consistency when the English vocabulary varies. From a &lt;strong&gt;process standpoint&lt;/strong&gt;, ensuring consistency across languages with natural keys means diligently updating every language when English changes (since the key changes), and keeping an eye on identical or similar English phrases that should perhaps be unified. Translation memory tools can help by suggesting translations for identical phrases, but they won’t inherently link them like a key would.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Key-based approach provides a clearer path to consistency across locales, since each key anchors a concept in all languages. It’s easier to maintain parity and detect divergences. Using English strings as keys can still yield consistency if managed well, but it relies more on discipline and translation memory to keep things aligned. If consistency and tight control over wording across languages is paramount (as it often is for branding or legal text), key-based is safer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Pluralization and Dynamic Content
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Key-Based:&lt;/strong&gt; Internationalization frameworks like i18next offer robust pluralization support when using keys. Typically, you define a base key and multiple forms, for example:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"unread_one"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You have 1 unread message."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"unread_other"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You have {{count}} unread messages."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;In code you call &lt;code&gt;t('mail.unread', { count: messagesCount })&lt;/code&gt; and i18next will pick the &lt;code&gt;_one&lt;/code&gt; or &lt;code&gt;_other&lt;/code&gt; form based on the count (&lt;a href="https://www.i18next.com/translation-function/plurals#:~:text=Languages%20with%20multiple%20plurals,key_one" rel="noopener noreferrer"&gt;Plurals - i18next documentation&lt;/a&gt;). This system relies on keys and suffixes to differentiate plural forms. It cleanly separates singular vs plural logic and lets translators handle language-specific pluralization (even for languages with multiple plural forms). Similarly, for gendered or contextual variants, you might use keys like &lt;code&gt;welcome_user_male&lt;/code&gt; vs &lt;code&gt;welcome_user_female&lt;/code&gt; or use i18next’s context feature, again leveraging the key system. For dynamic content, keys work in tandem with &lt;strong&gt;interpolation&lt;/strong&gt;: the key identifies the sentence template, and the translation string includes placeholders (like &lt;code&gt;{{username}}&lt;/code&gt;) that get replaced at runtime. i18next encourages this approach as it keeps grammatical order correct per language and avoids string concatenation in code (&lt;a href="https://www.i18next.com/principles/best-practices#:~:text=Implications%20of%20interpolation%20for%20localization" rel="noopener noreferrer"&gt;Best Practices | i18next documentation&lt;/a&gt;) (&lt;a href="https://www.i18next.com/principles/best-practices#:~:text=" rel="noopener noreferrer"&gt;Best Practices | i18next documentation&lt;/a&gt;). Overall, key-based translations handle plurals and variables in a structured way defined in the localization files (which is important because different languages have different grammar around those).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;English as Key:&lt;/strong&gt; It’s possible to manage plurals and dynamic content, but you often end up embedding the logic in the English strings themselves. For plurals, one way is to just treat the singular and plural as two separate keys (as mentioned earlier), e.g. &lt;code&gt;t("You have 1 unread message.")&lt;/code&gt; and &lt;code&gt;t("You have {{count}} unread messages.")&lt;/code&gt; depending on the count. This works, but it means the onus is on the developer to pick the right key for singular vs plural. You lose the automatic pluralization rules that i18n can provide, which can be error-prone when expanding to languages with more complex plural rules than English. Alternatively, you could still use i18next’s pluralization by setting a custom key or enabling ICU syntax. For instance, i18next supports ICU message format: you could have one key with an ICU string like &lt;code&gt;"You have {count, plural, one{# unread message} other{# unread messages}}"&lt;/code&gt; as the English translation. In code you’d call that key with a count. But note, here we introduced a synthetic key (or we treat the entire ICU string as the key which is awkward). Generally, using natural keys for singular/plural will push you toward writing more code logic to handle plurals, or leveraging ICU in translations (which is fine, but that’s essentially moving away from pure “English as the key” to “English as the default translation with a symbolic key”). For dynamic content, if you include placeholders in the key string, i18next will interpolate them even in the fallback. For example, &lt;code&gt;t("Hello, {{name}}!", { name: userName })&lt;/code&gt; would likely show &lt;code&gt;"Hello, John!"&lt;/code&gt; by replacing &lt;code&gt;{{name}}&lt;/code&gt; even if it's using the key as the output (since i18next treats the key as a default value here). This is convenient – you still get variable substitution. The caution is that the presence of placeholders might necessitate some context for translators (they need to know what &lt;code&gt;{{name}}&lt;/code&gt; represents), but that’s true in both approaches. In short, pluralization is the one area where key-based approach has a clear structural advantage, while dynamic content (interpolation) is handled similarly by both, with just some careful attention needed when using raw strings as keys.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Key-based approach is more aligned with how i18n frameworks handle plural and dynamic content out-of-the-box. It enables you to use the library’s full capabilities (plural rules per locale, etc.) without hacking the keys. The natural key approach can still work, but you may end up writing more conditional code for plurals or using more complex translation strings (ICU) to achieve the same result. If your application has a lot of pluralization or gendered phrases, you might lean towards key-based for clarity and correctness. For simple cases (like just two forms, English-like), natural keys won’t pose much issue as long as you’re careful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommendations for When to Use Each Approach
&lt;/h2&gt;

&lt;p&gt;Both approaches can be made to work with &lt;strong&gt;i18next in a TypeScript + React&lt;/strong&gt; stack, but the best choice depends on your project’s priorities and team workflow. Here are some recommendations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use &lt;em&gt;Key-Based Localization&lt;/em&gt; when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Your project is large or growing&lt;/strong&gt; – If you anticipate a lot of UI text or many languages, a key-based approach will scale better and remain manageable (&lt;a href="https://poeditor.com/blog/advantages-of-using-translation-keys/#:~:text=By%20assigning%20unique%20identifiers%20to,text%2C%20simplifying%20tasks%20such%20as" rel="noopener noreferrer"&gt;8 Advantages of using translation keys in your localization files - POEditor Blog&lt;/a&gt;). The structure helps avoid duplication and inconsistencies as the app expands.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Text will be iterated on&lt;/strong&gt; – If copy is likely to change due to UX research, marketing input, or frequent tweaks, it’s safer to use abstract keys so that changing the English (or any base language) doesn’t require code changes (&lt;a href="https://github.com/nodejs/i18n/issues/50#:~:text=Concept%20is%20that%20you%20define,it%20in%20the%20translation%20file" rel="noopener noreferrer"&gt;Key based i18n vs default language i18n · Issue #50 · nodejs/i18n · GitHub&lt;/a&gt;). This decoupling means product or content teams can suggest wording changes that developers implement just by updating translation files.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multiple contexts for similar text&lt;/strong&gt; – When the same or similar English words appear in different contexts (and might need different translations), key-based is preferable. You can encode context in keys (e.g. &lt;code&gt;menu.open&lt;/code&gt; vs &lt;code&gt;action.open&lt;/code&gt;) to guide translators (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=I%20like%20your%20second%20approach,and" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;), whereas using the raw text “Open” for both would create ambiguity. If your app has a lot of short phrases or reused terms, key-based gives you finer control.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You want consistency and reusability&lt;/strong&gt; – If the goal is to have a single source of truth for each piece of text and reuse translations across the app, use keys. For example, the text for “Cancel” button can be one key reused everywhere, ensuring every “Cancel” is translated identically. This also reduces translation workload (each language translates “Cancel” once) (&lt;a href="https://poeditor.com/blog/advantages-of-using-translation-keys/#:~:text=One%20of%20the%20most%20compelling,used%20consistently%20across%20various%20contexts" rel="noopener noreferrer"&gt;8 Advantages of using translation keys in your localization files - POEditor Blog&lt;/a&gt;). With natural strings, a dev might accidentally use “Cancel” in one place and “Abort” in another, leading to duplicate entries for translators.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Your team uses a TMS or formal process&lt;/strong&gt; – Most localization platforms (Locize, Transifex, Lokalise, etc.) and professional translators are very comfortable with key-based workflows. It’s often easier to integrate keys with things like screenshot management, context descriptions, and versioning of source text. If you’ll have dedicated translators or a localization team, they might prefer keys + English source strings as a clear separation of concerns (identifier vs content). Also, if your organization has a glossary or requires consistent terminology, managing that by key is more straightforward.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Type safety and refactoring are important&lt;/strong&gt; – In a TypeScript project, you might want to leverage type checking for translations. With key-based approach, you can use packages or scripts to generate a union of all keys, allowing &lt;code&gt;t()&lt;/code&gt; to be typed. This means if a developer uses a non-existent key, TypeScript will error – preventing runtime translation misses. It also makes refactoring keys (renaming) easier with find-and-replace, since keys are usually simpler strings without spaces/punctuation. Using entire sentences as keys is harder to type-check (the union of all sentences is huge and not practical to maintain manually).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Examples of when to choose key-based:&lt;/strong&gt; A complex enterprise app with forms, messages, tooltips across dozens of screens; a product where English copy is still being refined over time; an app where future translation to 5+ languages is planned; any scenario where you have translators working in parallel and you need strict control over context.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use &lt;em&gt;English (Natural) Keys&lt;/em&gt; when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rapid prototyping or early development&lt;/strong&gt; – If you’re at an early stage and need to get the UI up quickly in the default language, using the text directly can save time (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=We%27ve%20been%20using%20abstract%20placeholders,to%20think%20about%20naming%20placeholders" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;). You can worry about abstracting keys later (or not at all if it suffices). This is common in hackathons, MVPs, or internal tools where initial speed is valued over long-term elegance. i18next allows this by setting &lt;code&gt;fallbackLng&lt;/code&gt; to English and using the key as default text (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=1,placeholder%20as%20the%20translated%20text" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Small or one-off projects&lt;/strong&gt; – For a simple app or a static website with limited text, the overhead of managing separate keys might not be worth it. If there are only, say, 20 strings and they’re not likely to change much, using natural keys is fine and makes the code very clear. You could even decide to not have a separate JSON file for English at all – just use the strings in code and maintain translation files for other languages. The simplicity can trump the theoretical downsides since scope is limited.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Projects with gettext or similar i18n heritage&lt;/strong&gt; – If your team or tools come from a gettext background, using the string as the message ID is a normal practice. Libraries like react-i18next can support this mode (by disabling key separators and using the key as fallback text) to mimic gettext style. In such cases, you might have workflows to extract strings from code into a .pot file and merge translations. Sticking to English as keys could integrate better with those existing processes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;When developer clarity outweighs future proofing&lt;/strong&gt; – In some teams, especially those without dedicated localization folks, having the actual text in code can reduce misunderstandings. For example, a developer sees &lt;code&gt;t("Server error")&lt;/code&gt; and knows what it is, whereas &lt;code&gt;t("ERR_42_MSG")&lt;/code&gt; might lead them down a documentation hunt. If the team is small and you’re confident you can manage the occasional text change manually, the convenience for devs might win. Just be aware of the debt you incur (as discussed). This is often a judgment call. Some maintain that the time saved during development and debugging using natural keys more than offsets the time spent later if a change is needed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Content is truly fixed or auto-generated&lt;/strong&gt; – If the text is unlikely to ever need editing (perhaps regulatory text, or content that comes from elsewhere and is stable), using it directly is reasonable. Also, if keys would end up as nearly the full sentence anyway (because you’d make them descriptive to the point of being the sentence), then you might ask: why not just use the sentence? For example, if your key naming policy would produce something like &lt;code&gt;instructions.pleasePlaceDeviceOnFlatSurface&lt;/code&gt; for the text “Please place the device on a flat surface.”, some might prefer to avoid the indirection and just use the sentence.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Examples of when to choose natural keys:&lt;/strong&gt; A prototype or proof-of-concept where i18n is needed but likely only one language initially; a simple marketing site with a few translatable phrases; a backend-rendered template system where gettext is already used; a plugin or module intended to be easy for others to read and maybe contribute to without needing to understand a separate translation file.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Hybrid Approaches:&lt;/strong&gt; It’s worth noting you don’t strictly have to choose one style for everything, but you should avoid &lt;em&gt;mixing styles arbitrarily&lt;/em&gt; within the same project (&lt;a href="https://react.i18next.com/legacy-v9/step-by-step-guide#3-sidequest-natural-vs-keybased-catalog#:~:text=If%20you%20prefer%20natural%20or,Just%20avoid%20mixing%20those%20styles" rel="noopener noreferrer"&gt;Step by step guide (v9) | react-i18next documentation&lt;/a&gt;). Some teams adopt a hybrid: for &lt;strong&gt;UI labels and short text&lt;/strong&gt; they use natural keys (for readability), but for longer or reused text, they use semantic keys. If doing this, you must be disciplined to prevent confusion. Another variant is the “&lt;strong&gt;engineering English&lt;/strong&gt;” approach (&lt;a href="https://github.com/nodejs/i18n/issues/50#:~:text=Concept%20is%20that%20you%20define,it%20in%20the%20translation%20file" rel="noopener noreferrer"&gt;Key based i18n vs default language i18n · Issue #50 · nodejs/i18n · GitHub&lt;/a&gt;) – you use English phrases as keys initially, but once set, the key is treated as immutable. If the English needs to change, you change the English &lt;em&gt;translation&lt;/em&gt; for that key instead of the key itself. Essentially the key becomes an identifier that looks like English. For example, you might have &lt;code&gt;t("You need to use a charging adapter on this route.")&lt;/code&gt; as a key. Later, if you want to reword it, you keep the key string the same but in the English resource file you map that key to a new sentence (so the UI shows the new sentence). This preserves code and translation links at the cost of the key being somewhat misleading. This method can be useful if you started with natural keys and later realized you need stability – but it requires clear communication that the “key” is not literally the displayed text anymore. Generally, it’s cleaner to either use keys from the start or accept that changing natural keys will be a bit of work.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;i18next in a React+TypeScript project&lt;/strong&gt;, either approach can be configured:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;If you go key-based, define your namespaces and use &lt;code&gt;t('namespace:key')&lt;/code&gt; or structured keys with dots. Maintain an &lt;code&gt;en.json&lt;/code&gt; file with all keys and English strings (this acts as the source for translators). You might set up TypeScript types for the resources (using &lt;code&gt;react-i18next&lt;/code&gt; type augmentation or a codegen tool) to ensure keys exist at compile time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you go natural keys, initialize i18next with &lt;code&gt;keySeparator: false&lt;/code&gt; (and &lt;code&gt;nsSeparator: false&lt;/code&gt; if you plan to include &lt;code&gt;:&lt;/code&gt; in keys or use one global namespace) so that your sentence strings are not split on &lt;code&gt;.&lt;/code&gt; or &lt;code&gt;:&lt;/code&gt; (&lt;a href="https://react.i18next.com/legacy-v9/step-by-step-guide#3-sidequest-natural-vs-keybased-catalog#:~:text=This%20was%20possible%20by%20setting,i18n.init" rel="noopener noreferrer"&gt;Step by step guide (v9) | react-i18next documentation&lt;/a&gt;). You can still use an English JSON file if you want, but it may be redundant – instead, you might rely on &lt;code&gt;t(key, { defaultValue: key })&lt;/code&gt; or simply &lt;code&gt;t(key)&lt;/code&gt; with fallback to show the key when untranslated (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=1,placeholder%20as%20the%20translated%20text" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;). Ensure all developers know not to change key strings lightly. Also, consider using the &lt;code&gt;saveMissing&lt;/code&gt; option in i18next during development – it can collect any keys (in this case full sentences) that aren’t in the translation files and save them, which helps populate your base language file automatically (&lt;a href="https://www.i18next.com/translation-function/essentials#:~:text=In%20case%20you%27re%20using%20the,js%20example" rel="noopener noreferrer"&gt;Essentials | i18next documentation&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In either case, make use of i18next’s features like &lt;code&gt;t&lt;/code&gt; function options for plurals (&lt;code&gt;count&lt;/code&gt;) and interpolation (&lt;code&gt;{{var}}&lt;/code&gt;) rather than concatenating strings. For React specifically, the &lt;code&gt;&amp;lt;Trans&amp;gt;&lt;/code&gt; component can be useful for complex markup within translations, and it works with both styles of keys (you either pass a key or the default text inside the component). Just remain consistent with whichever key style you choose.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Best Practices for Maintainable and Developer-Friendly Translations
&lt;/h2&gt;

&lt;p&gt;No matter which approach you choose, the following best practices will help keep your internationalization workflow smooth and your codebase clean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Establish a Naming Convention (if using keys):&lt;/strong&gt; Decide on a clear scheme for your keys and stick to it project-wide (&lt;a href="https://www.reddit.com/r/vuejs/comments/176tyvz/i18n_do_you_prefer_using_one_big_translation/#:~:text=The%20i18n%20keys%20should%20be,how%20to%20create%20the%20keys" rel="noopener noreferrer"&gt;i18n: Do you prefer using one big translation dictionary for your whole app, or sprinkle translation strings inside your components? : r/vuejs&lt;/a&gt;) (&lt;a href="https://www.reddit.com/r/vuejs/comments/176tyvz/i18n_do_you_prefer_using_one_big_translation/#:~:text=Here%20you%20can%20see%20a,A%2C%20B%20and%20C" rel="noopener noreferrer"&gt;i18n: Do you prefer using one big translation dictionary for your whole app, or sprinkle translation strings inside your components? : r/vuejs&lt;/a&gt;). For example, you might use a &lt;code&gt;&amp;lt;scope&amp;gt;.&amp;lt;subscope&amp;gt;.&amp;lt;description&amp;gt;&lt;/code&gt; format (as in &lt;code&gt;"checkout.payment.errorCardDeclined"&lt;/code&gt;). Consistent patterns make keys easier to decode and avoid collisions. Include context in keys where needed (e.g., &lt;code&gt;"label.save"&lt;/code&gt; vs &lt;code&gt;"verb.save"&lt;/code&gt; if a word could be a noun or verb) (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=I%20like%20your%20second%20approach,and" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;). Document this convention so all contributors use it. Also, avoid overly generic keys (like &lt;code&gt;"message1"&lt;/code&gt;); keys should be descriptive enough to identify the content.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keep Translation Files Organized:&lt;/strong&gt; Use i18next &lt;strong&gt;namespaces&lt;/strong&gt; or separate JSON files per module/page. This prevents one huge file and reduces merge conflicts when multiple people add strings. For example, have &lt;code&gt;auth.json&lt;/code&gt; for authentication screens, &lt;code&gt;common.json&lt;/code&gt; for reusable common phrases, etc. In React, you can lazy-load namespaces as needed (i18next supports dynamic loading), but given maintainability is prioritized over performance here, it’s also fine to load all at once for simplicity. Organizing by feature also helps translators know where text is used, especially if your keys or source strings are not self-explanatory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use Default Values and Missing Key Logging:&lt;/strong&gt; During development, take advantage of i18next’s &lt;strong&gt;default value&lt;/strong&gt; feature to avoid double-writing strings in code and files. For instance, &lt;code&gt;t('errors.networkTimeout', { defaultValue: 'The request timed out.' })&lt;/code&gt; will display the default text if that key isn’t yet in your translation file (&lt;a href="https://www.i18next.com/translation-function/essentials#:~:text=You%20can%20pass%20in%20a,be%20found%20in%20translations%20like" rel="noopener noreferrer"&gt;Essentials | i18next documentation&lt;/a&gt;). Coupled with &lt;code&gt;saveMissing: true&lt;/code&gt; in i18next config, it can even send that default text to your translation storage (or console log it) so you know to add it (&lt;a href="https://www.i18next.com/translation-function/essentials#:~:text=In%20case%20you%27re%20using%20the,js%20example" rel="noopener noreferrer"&gt;Essentials | i18next documentation&lt;/a&gt;). This way, developers can add new translations in one step. Just remember to remove default values in production or ensure your localization process captures them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Avoid Hard-Coding Text:&lt;/strong&gt; Apart from perhaps some very static content, all user-facing strings should go through the i18n system. This ensures nothing is missed when translating. Run ESLint or other linters to catch accidental hard-coded literals in the code. For example, there are ESLint plugins for i18next or for frameworks (like vue-i18n) that warn if you have raw text in JSX that isn’t wrapped in a &lt;code&gt;&amp;lt;Trans&amp;gt;&lt;/code&gt; or &lt;code&gt;t()&lt;/code&gt; call (&lt;a href="https://www.reddit.com/r/vuejs/comments/176tyvz/i18n_do_you_prefer_using_one_big_translation/#:~:text=kept%20in%20the%20single%20file,i18n%3E%20%E2%A4%B4%EF%B8%8F%20tags" rel="noopener noreferrer"&gt;i18n: Do you prefer using one big translation dictionary for your whole app, or sprinkle translation strings inside your components? : r/vuejs&lt;/a&gt;). This enforces consistency and makes future maintenance easier (no “forgotten” string that only exists in code).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Provide Context to Translators:&lt;/strong&gt; If using key-based, always supply the translators with an &lt;strong&gt;English reference&lt;/strong&gt; for each key (usually your English JSON) and any necessary comments (many TMS allow commenting on keys). Even if using natural keys, add context if the usage isn’t obvious. For example, if a string is “Draft”, clarify whether it’s a noun or verb, or maybe provide a screenshot of the screen. Context can also include notes about placeholders (e.g., “&lt;code&gt;{{count}}&lt;/code&gt; is a number of items”) or character limits if any. This extra effort prevents mis-translations and reduces back-and-forth. Remember, even with natural keys, a phrase taken out of UI context can be interpreted in multiple ways, so a brief note can help.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Handle Plurals and Gender Properly:&lt;/strong&gt; Use i18next’s pluralization features rather than writing logic in code to pick singular/plural strings. This means defining plural forms in your translation files (or using ICU messages) so that languages with complex plural rules are supported. Test your pluralization with a language like Russian or Arabic (which have 3-4 plural forms) to ensure your approach covers those cases (&lt;a href="https://www.i18next.com/translation-function/plurals#:~:text=Languages%20with%20multiple%20plurals,key_one" rel="noopener noreferrer"&gt;Plurals - i18next documentation&lt;/a&gt;). Similarly, if your app needs gendered terms or other contextual variants, consider using the i18next context feature (keys with suffixes like &lt;code&gt;_male&lt;/code&gt; &lt;code&gt;_female&lt;/code&gt;) or separate keys for each. The goal is to let the translation system, not the application logic, handle these variations as much as possible (&lt;a href="https://www.i18next.com/principles/best-practices#:~:text=When%20translating%20into%20other%20languages%2C,grammar%20rules%2C%20which%20is%20rare" rel="noopener noreferrer"&gt;Best Practices | i18next documentation&lt;/a&gt;) (&lt;a href="https://www.i18next.com/principles/best-practices#:~:text=" rel="noopener noreferrer"&gt;Best Practices | i18next documentation&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use Descriptive Variable Names in Strings:&lt;/strong&gt; When you have dynamic content, prefer named interpolation like &lt;code&gt;{{username}}&lt;/code&gt; over positional or generic placeholders. This makes the meaning clear to translators. For instance, &lt;code&gt;"Hello {{username}}, you have {{count}} new messages"&lt;/code&gt; is better than &lt;code&gt;"Hello %s, you have %d new messages"&lt;/code&gt;. It reduces confusion and allows changing word order if needed (translators can move the &lt;code&gt;{{count}}&lt;/code&gt; placeholder as appropriate for their grammar). i18next by default uses the &lt;code&gt;{{var}}&lt;/code&gt; syntax which is good (&lt;a href="https://www.reddit.com/r/vuejs/comments/176tyvz/i18n_do_you_prefer_using_one_big_translation/#:~:text=Scan%20or%20type%20in%20,this%20page%20is%20location%20above" rel="noopener noreferrer"&gt;i18n: Do you prefer using one big translation dictionary for your whole app, or sprinkle translation strings inside your components? : r/vuejs&lt;/a&gt;). Just ensure the variable names are meaningful (&lt;code&gt;{{count}}&lt;/code&gt;, &lt;code&gt;{{name}}&lt;/code&gt;, &lt;code&gt;{{totalPages}}&lt;/code&gt;, etc.). This is a general i18n best practice that improves localization quality.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consistent Formatting and Punctuation:&lt;/strong&gt; Decide whether keys should include punctuation or not, and be consistent. For example, some teams omit the period at the end in the key and only include it in the translation, to avoid keys differing only by punctuation. Others include full punctuation in keys for clarity. If using natural keys, you might run into issues with certain characters (like colons, dots) because i18next might interpret them as separators. You can escape them or adjust the config (e.g., set &lt;code&gt;nsSeparator: false&lt;/code&gt; if you have &lt;code&gt;:&lt;/code&gt; in keys like &lt;code&gt;UNT:BannerBody…&lt;/code&gt;) (&lt;a href="https://react.i18next.com/legacy-v9/step-by-step-guide#3-sidequest-natural-vs-keybased-catalog#:~:text=This%20was%20possible%20by%20setting,i18n.init" rel="noopener noreferrer"&gt;Step by step guide (v9) | react-i18next documentation&lt;/a&gt;). The key point is to standardize how you handle it so you don’t accidentally have two keys where one has a punctuation or newline difference. For maintainability, treat the text consistently.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automate Where Possible:&lt;/strong&gt; Utilize tools to lighten the translation maintenance burden. For example, use the i18next parser or similar to extract strings/keys from code into translation files, so nobody forgets to add them. Set up CI checks to warn if a translation key is missing in any language (or at least in the default language). If using TypeScript with key-based, set up the type generation so that as soon as you add a key to en.json, it’s available in the &lt;code&gt;t()&lt;/code&gt; function types (there are community tools for this, or you can write a simple script reading JSON and generating a d.ts file). Automation ensures your code and translation files don’t drift out of sync and reduces manual errors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Review and Refactor:&lt;/strong&gt; Treat your i18n content as part of the codebase that needs occasional refactoring. For key-based projects, that might mean cleaning up unused keys (over time, some keys might no longer be used in code – remove them to avoid confusion). For natural key projects, it could mean identifying and merging duplicate strings or clarifying ambiguous ones (maybe by switching a couple to use a context key instead). Also, periodically review if the chosen approach is still serving the project well. It’s possible to migrate from natural keys to structured keys later (via script or gradually) if you outgrow the initial method. It’s harder to go the other way around though. In any case, keeping the translation files tidy and logically structured improves maintainability significantly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test in Multiple Languages:&lt;/strong&gt; Don’t wait until the last minute to see your app in other languages. Even if you only have English initially, try adding a second locale (even a dummy or pseudo-translation) early on. This will help catch issues like keys not being found, text concatenation problems, or layout issues with longer text. It also forces developers to think about translation impact. If you’re using English as keys, switch the locale to something else (say French) and see if everything still makes sense (likely you’ll see English because it’s falling back to keys, which is fine). If using keys, maybe do a fake locale where every string is prefixed to ensure all keys are going through translation. The point is to integrate i18n testing into your routine so that when real translations come in, the system is robust.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prioritize Clarity over Premature Optimization:&lt;/strong&gt; Since the focus is maintainability and dev ease over performance, it’s okay to choose the approach that makes the code cleaner and the team happier, even if it’s not the most CPU or memory efficient. For example, having longer, descriptive keys or default texts might use a bit more memory, but it reduces confusion. Loading all translations at once might use more bandwidth, but simplifies development – you don’t have to worry about splitting or lazy loading messages. You can always optimize later if needed (like extracting only used keys per page), but a correct and clear implementation is the first goal. i18next is quite fast for reasonable numbers of keys, so optimize for developer productivity first.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In conclusion, &lt;strong&gt;using localization keys vs. direct text keys each has trade-offs&lt;/strong&gt;. Key-based catalogs offer stronger long-term maintainability, scalability, and flexibility for changes (&lt;a href="https://www.reddit.com/r/godot/comments/1hxjzs2/using_english_text_as_the_translation_csv_files/#:~:text=%E2%80%A2%20%E2%80%A2%20Edited" rel="noopener noreferrer"&gt;Using english text as the translation csv file's keys. Good or terrible idea? : r/godot&lt;/a&gt;) (&lt;a href="https://www.reddit.com/r/godot/comments/1hxjzs2/using_english_text_as_the_translation_csv_files/#:~:text=What%20if%20two%20places%20have,context%20and%20need%20different%20translations" rel="noopener noreferrer"&gt;Using english text as the translation csv file's keys. Good or terrible idea? : r/godot&lt;/a&gt;), making them ideal for larger projects and collaborative translation workflows. Direct text keys provide simplicity, immediacy, and code readability that can accelerate development and reduce friction (&lt;a href="https://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders#:~:text=We%27ve%20been%20using%20abstract%20placeholders,to%20think%20about%20naming%20placeholders" rel="noopener noreferrer"&gt;internationalization - Why do people use plain english as translation placeholders? - Stack Overflow&lt;/a&gt;) (&lt;a href="https://www.reddit.com/r/vuejs/comments/176tyvz/i18n_do_you_prefer_using_one_big_translation/#:~:text=It%20simply%20makes%20the%20codebase,easier%20to%20understand%20and%20read" rel="noopener noreferrer"&gt;i18n: Do you prefer using one big translation dictionary for your whole app, or sprinkle translation strings inside your components? : r/vuejs&lt;/a&gt;), which is attractive for small projects or early stages. Evaluate your project’s needs: if you foresee lots of evolution and numerous locales, lean towards key-based with good practices; if you need to move fast and the content domain is fairly static, natural keys can serve you well initially.&lt;/p&gt;

&lt;p&gt;For an i18next + TypeScript + React setup, either route can be implemented – just keep consistency and use i18next’s features to your advantage. Whichever approach you choose, apply the best practices above to maintain a clean, efficient, and developer-friendly localization codebase. Internationalization is as much about process as technology, so choose the approach that best aligns with your team’s workflow and the app’s future requirements, and be ready to adapt as the project grows.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>i18n</category>
      <category>localization</category>
    </item>
    <item>
      <title>Making API Calls from Node.js</title>
      <dc:creator>spyke</dc:creator>
      <pubDate>Thu, 18 May 2023 07:46:57 +0000</pubDate>
      <link>https://dev.to/spyke/making-api-calls-from-nodejs-3mfk</link>
      <guid>https://dev.to/spyke/making-api-calls-from-nodejs-3mfk</guid>
      <description>&lt;p&gt;Calling another API from Node.js is straightforward.&lt;/p&gt;

&lt;p&gt;Starting with Node.js version 18, a web-compatible &lt;code&gt;fetch()&lt;/code&gt; function is available out of the box. However, as it is still marked as &lt;code&gt;experimental&lt;/code&gt;, there are battle-tested libraries like &lt;a href="https://github.com/axios/axios" rel="noopener noreferrer"&gt;Axios&lt;/a&gt; suitable for &lt;em&gt;production&lt;/em&gt; environments, offering  convenient APIs and a comprehensive feature set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// An equivalent to `GET /users?id=12345`&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12345&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Happy coding!&lt;/p&gt;




&lt;h2&gt;
  
  
  Expanding the Scope
&lt;/h2&gt;

&lt;p&gt;With the basics covered, let's discuss how to scale this solution. Not in terms of algorithmic or space complexity - which is often the focus of job interviews - but in terms of scaling it for a large application that will be worked on by a team of developers over several years. This is the kind of scaling we deal with in day-to-day work.&lt;/p&gt;

&lt;p&gt;To illustrate this, we'll use a real-world API from a digital bank with public documentation: &lt;a href="https://docs.solarisgroup.com/api-reference" rel="noopener noreferrer"&gt;https://docs.solarisgroup.com/api-reference&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclaimer: I am not affiliated with Solaris SE. I just happened to be familiar with this API. Using realistic examples can be beneficial.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Fetching Person Data
&lt;/h2&gt;

&lt;p&gt;Let's begin with a basic &lt;a href="https://docs.solarisgroup.com/api-reference/onboarding/persons/#tag/Persons/paths/~1v1~1persons~1{id}/get" rel="noopener noreferrer"&gt;getPerson&lt;/a&gt; call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;API_BASE_URL&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPerson_v0&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;personId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`v1/persons/{id}`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, it's not significantly different from the code in the &lt;em&gt;introductory&lt;/em&gt; section. However, considering the returned value (see the response object in the docs), you might spot some issues.&lt;/p&gt;

&lt;p&gt;Firstly, &lt;code&gt;axios&lt;/code&gt; returns its own response object. Passing this up the stack isn't ideal as our callers (users of the API client we make) don't need to know what HTTP library we're using. We want to retain the flexibility to replace it later. Moreover, the users are primarily interested in the &lt;code&gt;Person&lt;/code&gt; object itself, which is contained in the &lt;code&gt;data&lt;/code&gt; property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPerson_v1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;personId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`v1/persons/{id}`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's consider the &lt;code&gt;Person&lt;/code&gt; value itself:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keys are in &lt;code&gt;snake_case&lt;/code&gt;, which can lead to warnings from &lt;code&gt;ESLint&lt;/code&gt; and complaints from other developers because our standard is &lt;code&gt;camelCase&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Dates are represented as strings.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPerson_v2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;personId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`v1/persons/{id}`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;snakeToCamelCase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;restoreDates&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code, &lt;code&gt;snakeToCamelCase&lt;/code&gt; and &lt;code&gt;restoreDates&lt;/code&gt; are utility functions that iterate through the object's keys and map either the key or the value, depending on what needs to be corrected. The details of their implementation are not the focus of our discussion.&lt;/p&gt;

&lt;p&gt;I've also added &lt;code&gt;&amp;lt;Person&amp;gt;&lt;/code&gt; to the type parameters. Naturally, we need to create interfaces and enumerations for all data that we send or retrieve. These could be generated from an OpenAPI spec or created manually, depending on what the docs provide.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Unexpected Value
&lt;/h2&gt;

&lt;p&gt;We restore date values, but we don't need to restore enumerations like &lt;code&gt;employment_status&lt;/code&gt; as they are just strings. But what if a new unexpected value appears in the response that we don't have in our &lt;code&gt;EmploymentStatus&lt;/code&gt;? Or even worse, some other field becomes a &lt;code&gt;string&lt;/code&gt; instead of a &lt;code&gt;number&lt;/code&gt;. We'll probably corrupt the application state at runtime.&lt;/p&gt;

&lt;p&gt;An API is a living creature; it changes over time. It also has bugs (everything has bugs). We need to defend our code from invalid response data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Schema Validation
&lt;/h2&gt;

&lt;p&gt;Schema validation can ensure that the data your application is receiving is of the correct type and format. This can help you catch bugs and discrepancies in the API, as well as protect your application from potential crashes or corrupt states due to unexpected data.&lt;/p&gt;

&lt;p&gt;For Node.js, libraries such as &lt;code&gt;Joi&lt;/code&gt;, &lt;code&gt;Yup&lt;/code&gt;, &lt;code&gt;Zod&lt;/code&gt; are often used. I will be using &lt;a href="https://github.com/pelotom/runtypes" rel="noopener noreferrer"&gt;Runtypes&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;runtypes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PersonSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// Other fields are omitted for simplicity&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;birth_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;InstanceOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;employment_status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EmploymentStatusSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Static&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;PersonSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPerson_v3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;personId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`v1/persons/{id}`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;snakeToCamelCase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;restoreDates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;PersonSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calling &lt;code&gt;.check(person)&lt;/code&gt; either returns back a typed Person or fails with an error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication
&lt;/h2&gt;

&lt;p&gt;We need to address authentication as well. The API is protected by a bearer token, which you can get via the oAuth endpoint. The token has an expiration time, and we will need to refresh it from time to time. I'm going to encapsulate all business logic about tokens in the &lt;code&gt;TokenManager&lt;/code&gt; component. Read as "I will mostly skip it":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TokenManager&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;getToken&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is really a separate client, which knows how to call the token endpoint and is using &lt;code&gt;setTimeout&lt;/code&gt; to refresh the token prematurely. &lt;code&gt;getToken&lt;/code&gt; returns a promise with the token value.&lt;/p&gt;

&lt;p&gt;Axios provides &lt;code&gt;interceptors&lt;/code&gt; as a mechanism, which we will utilize for injecting the token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokenManager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TokenManager&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;injectNewToken&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="nx"&gt;C&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;InternalAxiosRequestConfig&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;AxiosRequestConfig&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tokenManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getToken&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;setAuthorization&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAuthorization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;httpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;interceptors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;injectNewToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;httpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;interceptors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isAxiosError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;injectNewToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;httpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code is simplified, but what we're doing is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Before actually dispatching a request, we ask &lt;code&gt;TokenManger&lt;/code&gt; to give us a valid token, then we add it as the &lt;code&gt;Authorization&lt;/code&gt; header to the request config.&lt;/li&gt;
&lt;li&gt;In case of an error response with a 401 status code, we know from the documentation that the token was expired by the time it arrived at the API, and we need to get a new one and repeat the request.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Errors
&lt;/h2&gt;

&lt;p&gt;The API can respond with more than just 401 errors. Even more importantly, there's a whole page describing the error response format. At a bare minimum, we need to parse the response and throw something like &lt;code&gt;new Error(responseError.code)&lt;/code&gt;. But we will need the error ID when we write to their support (and we will, a lot). Error description is helpful as well for debugging the issues.&lt;/p&gt;

&lt;p&gt;The users of our client library should not dig into Axios errors and responses to understand that the person is not found. They should be able to easily pattern match on different error situations.&lt;/p&gt;

&lt;p&gt;Introducing custom errors and error mapping. This is where I will be simplifying even more. Otherwise, it will be a thousand lines of code. For example, I will assume that there could be only 1 error in the response. And we will map only error codes, ignoring status codes and possible combinations.&lt;/p&gt;

&lt;p&gt;Errors begin with making types and schemas for server error responses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;SolarErrorCode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;METHOD_NOT_ALLOWED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;method_not_allowed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;MODEL_NOT_FOUND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;model_not_found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;UNAUTHORIZED_ACTION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unauthorized_action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SolarErrorDataSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// Other fields are omitted for simplicity&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SolarErrorResponseSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// Other fields are omitted for simplicity&lt;/span&gt;
  &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SolarErrorDataSchema&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that we can define our own error class, which will be used by the code calling our client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;ClientErrorCode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;CONFIG_ISSUE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;config_issue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;MODEL_NOT_FOUND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;model_not_found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;NO_CONNECTION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no_connection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;UNAUTHORIZED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unauthorized&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;UNEXPECTED_ERROR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unexpected_error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClientError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;ClientError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;ClientError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;mapCode&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;codeMap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ClientError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;clientErrorMapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ClientError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;remapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;codeMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;codeMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;remapper&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;remapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;errorData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SolarErrorData&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;Pick&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SolarErrorData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;detail&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errorData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;errorData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;errorData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;errorData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how to &lt;em&gt;pattern match&lt;/em&gt; a &lt;code&gt;ClientError&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPerson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;ClientError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mapCode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ClientErrorCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CONFIG_ISSUE&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Another error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ClientErrorCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UNEXPECTED_ERROR&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here users would be able to remap client errors to their own internal errors. Also, if you look at the inferred type of the &lt;code&gt;person&lt;/code&gt;, it would be &lt;code&gt;Person | undefined | 123&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But where does this &lt;code&gt;ClientError&lt;/code&gt; come from? We parse error responses and map them into appropriate values (like &lt;code&gt;undefined&lt;/code&gt; instead of 404) or &lt;code&gt;ClientError&lt;/code&gt; instances depending on what is required. The goal is to hide the transport or API-specific stuff.&lt;/p&gt;

&lt;p&gt;Here is the helper. Most of the conditions (&lt;code&gt;if&lt;/code&gt; blocks) come from how you should &lt;a href="https://axios-http.com/docs/handling_errors" rel="noopener noreferrer"&gt;handle Axios errors&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mapAxiosError&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errorMap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;ClientErrorCode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;axiosErrorMapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isAxiosError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SolarErrorResponseSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;valueMapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;errorMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;firstError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;errorMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;valueMapper&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;valueMapper&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ClientError&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;firstError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;valueMapper&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;valueMapper&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ClientErrorCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UNEXPECTED_ERROR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;firstError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;firstError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ClientError&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ClientErrorCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NO_CONNECTION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Connection failed, please check your network&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ClientError&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ClientErrorCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CONFIG_ISSUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Request configuration is invalid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The helper again, but applied to &lt;code&gt;getPerson&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPerson_v4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;personId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;httpClient&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`v1/persons/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;personId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;snakeToCamelCase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;restoreDates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;PersonSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;mapAxiosError&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SolarErrorCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MODEL_NOT_FOUND&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SolarErrorCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UNAUTHORIZED_ACTION&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;ClientErrorCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UNAUTHORIZED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ClientErrorCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UNEXPECTED_ERROR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, half of the code in the &lt;code&gt;getPerson&lt;/code&gt; would be repeated in other API functions, so we need to extract that code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TypedRequestConfig&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;AxiosRequestConfig&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Runtype&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;makeRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TypedRequestConfig&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;httpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;snakeToCamelCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;restoreDates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which removes a lot of duplication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPerson_v5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;personId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;makeRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`v1/persons/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;personId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PersonSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;mapAxiosError&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SolarErrorCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MODEL_NOT_FOUND&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SolarErrorCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UNAUTHORIZED_ACTION&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;ClientErrorCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UNAUTHORIZED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ClientErrorCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UNEXPECTED_ERROR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;I bet you are already tired of writing code. But there's still so much to do:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; A circuit breaker and retries. If the network fails, it is convenient to automatically retry. But you need to control this behavior and prevent infinite loops, especially with 401 responses and invalid tokens.&lt;/li&gt;
&lt;li&gt;Logs for debugging. Bad things happen, and you will need to debug the app, probably even in a live environment. It is nice to have some way to enable logs showing requests and responses.&lt;/li&gt;
&lt;li&gt;Telemetry. You probably want to collect response times and other data and sent it to Prometheus or another system.&lt;/li&gt;
&lt;li&gt;Request overrides. There should be a way to attach additional headers, set a specific timeout, etc., for every API function individually.&lt;/li&gt;
&lt;li&gt;Distributed tracing. This could be done through request overrides, but you probably want to set it up once during client initialization.&lt;/li&gt;
&lt;li&gt;Get rid of &lt;code&gt;process.env&lt;/code&gt;. Use a reliable config component like &lt;a href="https://github.com/af/envalid" rel="noopener noreferrer"&gt;envalid&lt;/a&gt;. Use &lt;code&gt;lodash&lt;/code&gt; or another library instead of &lt;code&gt;typeof&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Who knows what else.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What I want you to understand is that making a production-grade client is difficult. You can definitely call an API from Node.js as shown at the beginning of this post. But after a couple of years of work of multiple developers or even teams, the app and the client will become enormous. Unmanaged and accidental complexity will silently waste the time of every developer when they add a new function, update it, or fix a bug. And it will not be visible on Jira charts. So, at some point in time you will need to refactor and you may use this post for inspiration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus Content
&lt;/h2&gt;

&lt;p&gt;If you look at the &lt;code&gt;getPerson_v4&lt;/code&gt;, you might notice that response processing is like a &lt;strong&gt;data pipeline&lt;/strong&gt; with multiple steps. A successful response has its own steps, while an error response has its own. These steps are simple.&lt;/p&gt;

&lt;p&gt;Speaking of steps, if we were to create a function for something like &lt;code&gt;POST&lt;/code&gt; (where we send data to the API), we would have one more pipeline. This pipeline would process the request body: converting &lt;code&gt;camelCase&lt;/code&gt; to &lt;code&gt;snake_case&lt;/code&gt;, and so on. It's similar to the response pipeline, but in the opposite direction.&lt;/p&gt;

&lt;p&gt;You've seen data pipelines before. In &lt;code&gt;express&lt;/code&gt;, request processing is a data pipeline. Every middleware somehow affects or transforms the request until it reaches your handler (controller).&lt;/p&gt;

&lt;p&gt;Many of the recommendations I made are applicable to frontend and universal clients too.&lt;/p&gt;

&lt;p&gt;The full source code is available on &lt;a href="https://codesandbox.io/p/sandbox/solaris-client-example-lmpc1n?file=%2Fsrc%2Fexample.ts" rel="noopener noreferrer"&gt;CodeSandbox&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>node</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Searching through JSON logs with Node.js</title>
      <dc:creator>spyke</dc:creator>
      <pubDate>Sat, 04 Mar 2023 20:29:44 +0000</pubDate>
      <link>https://dev.to/spyke/searching-through-json-logs-locally-2k0m</link>
      <guid>https://dev.to/spyke/searching-through-json-logs-locally-2k0m</guid>
      <description>&lt;p&gt;I had to do a search through logs from our backend on my laptop. We have about 40 various web services that write logs in JSON format. And these logs together with logs from sidecars then streamed to Sematext (which is a managed &lt;a href="//elastic.co/what-is/elk-stack"&gt;ELK&lt;/a&gt; service). After a retention period logs got chunked into LZ4 compressed text files and backed up to an AWS S3 bucket.  &lt;/p&gt;

&lt;p&gt;The problem? There're a lot of logs. Every such archive is ~1 MB is size. It contains about 5,000 of JSON records. 1 month of logs is about 100k of files and about 100 GB in size.&lt;/p&gt;

&lt;p&gt;Unfortunately, there was no established process on the project. A couple of team mates had experience with logs a year ago and all they did was: download, unpack, grep.&lt;/p&gt;

&lt;p&gt;In my case grepping wasn't the best option as I needed not only to find the specific request (log), but also the review its distributed trace and context.&lt;/p&gt;

&lt;p&gt;I had 2 options on my mind: local &lt;code&gt;ELK&lt;/code&gt; and &lt;a href="https://lnav.org/" rel="noopener noreferrer"&gt;lnav&lt;/a&gt;. I tried to google about how to quickly run ELK in Docker locally (I had no prior experience in setting ELK up), but haven't found good tutorials. I believe they exist, but I also was thinking about possible caveats.&lt;/p&gt;

&lt;p&gt;Our logs are not unified: some services populate different field names like &lt;code&gt;status&lt;/code&gt; vs. &lt;code&gt;statusCode&lt;/code&gt;, and they may also be of a different type (number vs. string). Writing ElasticSearch preprocessors in &lt;code&gt;painless&lt;/code&gt; is not so painless. And not everything could be done there. I know this as I was setting up Sematext Pipelines before.&lt;/p&gt;

&lt;p&gt;So, I've downloaded 112GB of logs for 1 month and tried to tame &lt;code&gt;lnav&lt;/code&gt; instead. Unfortunately, &lt;code&gt;lnav&lt;/code&gt; couldn't open my &lt;code&gt;lz4&lt;/code&gt; files because of some &lt;code&gt;unexpected end of stream&lt;/code&gt; issues. &lt;code&gt;lz4&lt;/code&gt; command line utility produces such errors as well, but still decompresses fine. But uncompressed files are 8 MB instead of 1 MB (112GB --&amp;gt; 896GB?). I tried on an uncompressed directory with just couple of files and it was overwhelming. I was reading through &lt;code&gt;lnav&lt;/code&gt; docs when saw that you may also define a scheme and load logs into an internal SQLite database.&lt;/p&gt;

&lt;p&gt;This is also where I saw a new shiny bicycle 🚲 But at least it could be &lt;em&gt;fun&lt;/em&gt; in comparison with boring docs...&lt;/p&gt;

&lt;p&gt;I'm a JavaScript dev: loading/parsing files is easy. I also could easily transform all logs to a unified format and throw away fields that I don't need like Kubernetes labels, etc. And our team is used to Postgres. If I upload all the data into a local DB, it might be very convenient. And I could sort everything properly, because logs in files are sorted in order of streaming from multiple sources, not by actual timestamps. So many benefits!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.memegenerator.net%2Finstances%2F56610968.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.memegenerator.net%2Finstances%2F56610968.jpg" alt="How hard can it be" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I settled on a very simple streamed pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;glob --&amp;gt; lz4 --&amp;gt; createReadStream --&amp;gt; readline
  --&amp;gt; transform --&amp;gt; filter --&amp;gt; insert
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating CLIs with &lt;a href="http://yargs.js.org/" rel="noopener noreferrer"&gt;yargs&lt;/a&gt; is a piece of cake. For LZ4 there is a streaming implementation on &lt;a href="https://www.npmjs.com/package/lz4" rel="noopener noreferrer"&gt;npm&lt;/a&gt; which is a single  function and also happened to decompress my files without any errors. Everything else is out-of-box Node.js.&lt;/p&gt;

&lt;p&gt;Transformation itself is just creating a new object based on parsed JSON. I know all 12 fields I will need. Creating a DDL for Postgres table didn't took much time as it's structure is the same as TS interface I already defined.&lt;/p&gt;

&lt;p&gt;Interestingly, every log has a natural Primary Key, because logs are in files, which are static. Backups only add up, no files are modified. We could use &lt;code&gt;file_name&lt;/code&gt; + &lt;code&gt;line_number&lt;/code&gt; to identify any log record. Why do we need this? For an incremental workflow. Inserting into a DB is very slow and is the main bottleneck. Of course, we could optimize it by using multi-value insert, disable triggers and constraints, etc. But I only disabled WAL for the table (as I will not modify row and can restore data) and decided to load data gradually:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Filter incoming records in JS to find the place in time where your issue(s) happened. For example, load only DELETE requests.&lt;/li&gt;
&lt;li&gt;Use SQL GUI to view/filter this data, sort by time.&lt;/li&gt;
&lt;li&gt;When time spans of interest are identified, tweak glob expression to load additional files for these time spans without filtering or with a different filer.&lt;/li&gt;
&lt;li&gt;Repeat.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Filter is a function in the TS file, which gets a transformed record and returns a boolean. If it's &lt;code&gt;true&lt;/code&gt;, the tool executes an &lt;code&gt;INSERT&lt;/code&gt; command with Postgres' extension &lt;code&gt;ON CONFLICT DO NOTHING&lt;/code&gt;. So, even if you try to load the same file multiple times, you would not get duplicate records thanks to the natural PK.&lt;/p&gt;

&lt;p&gt;It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;yarn ts-node ./log-loader.ts &lt;span class="s1"&gt;'./aws/2021/02/**/*.lz4'&lt;/span&gt;
...
Loading file &lt;span class="o"&gt;[&lt;/span&gt;159432/159438]: ./aws/2021/02/24/21/20210224T214041.318Z_5950e47a96ee741360bedbf6153ab069.json.lz4
...
Done, 159,438 files loaded, 1,095,380,480 lines parsed, 241,584 records added
✨  Done &lt;span class="k"&gt;in &lt;/span&gt;7572.51s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Time varies greatly with the number of inserts. Still, it's not exactly instant, but is fast enough for my use case. After a couple of iterations I got proper distributed traces for several requests of my interest in the DB and found who called what and why. I saved time not in &lt;em&gt;grepping&lt;/em&gt;, but in a nice GUI to look at final logs and understanding call stacks.&lt;/p&gt;

&lt;p&gt;A couple of month gave me 397,785 final rows. SQL queries work quite fast even without indexes: a search by URL prefix took only 2.278 sec.&lt;/p&gt;

&lt;p&gt;Actually, loading local files is not enough. I have only 512  GB on my laptop SSD and all it can fit is 3 month. So, for some cases I had to load data directly from AWS S3. It is slower for multiple runs, but I couldn't use external drive or other computer.&lt;/p&gt;

&lt;p&gt;Adding S3 support was very easy too. I just extracted &lt;code&gt;fs/glob&lt;/code&gt; code into a &lt;code&gt;fsAdapter.ts&lt;/code&gt; and added &lt;code&gt;awsAdapter.ts&lt;/code&gt;. If you specify &lt;code&gt;--aws&lt;/code&gt; option, then the positional argument of CLI is treated as bucket + prefix and the AWS adapter is used. From AWS client you only need &lt;code&gt;listObjects&lt;/code&gt; and &lt;code&gt;getObject&lt;/code&gt;, after which you obtain the same Readable stream as you had with the file system.&lt;/p&gt;

&lt;p&gt;Next time I might try to set up a local ELK to get a similar experience to not yet backed up logs (nice UI). As for the current project... I'm curious how many years the &lt;code&gt;log-loader&lt;/code&gt; will be in use by my colleagues if the &lt;em&gt;manual&lt;/em&gt; way lasted for almost 6 years.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>node</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Readying JavaScript packages for better future</title>
      <dc:creator>spyke</dc:creator>
      <pubDate>Mon, 30 Nov 2020 14:28:21 +0000</pubDate>
      <link>https://dev.to/spyke/readying-javascript-packages-for-better-future-339m</link>
      <guid>https://dev.to/spyke/readying-javascript-packages-for-better-future-339m</guid>
      <description>&lt;p&gt;Modern web applications consist of hundreds and thousands of files of different kinds. To manage this complexity and streamline app delivery developers started using the same approach as on desktops: compile &amp;amp; link. In relation to a web application compilation usually means transpiling, while linking means bundling.&lt;/p&gt;

&lt;p&gt;Same goes for libraries and other packages that web applications depend on and install from registries like npm. But the actual code in these registries is almost always exists in a form of the old ES5. It is harder to read and debug such code, it may run worse on modern engines, and it will be down compiled one more time during the app build process.&lt;/p&gt;

&lt;p&gt;Publishing ES5 code isn't that necessary anymore and may  be avoided. An app could rely on the code in it's original form while providing better efficiency, dead code elimination, and easier debugging experience.&lt;/p&gt;

&lt;p&gt;Babel became a &lt;em&gt;de facto&lt;/em&gt; tool for down compiling JavaScript code to older versions and could be used as an integration point. Combining it with ECMAScript Modules and staged JavaScript development process governed by TC39 group it is possible to define a set of rules for how to publish and consume packages in their original form.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Raw Module Specification&lt;/code&gt; or &lt;code&gt;RMS&lt;/code&gt; does exactly that. It is a convention for modern JavaScript packages and modules aiming to avoid excess code recompilation and deoptimization, retaining code readability and ease of debugging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Specification
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Package requirements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Package &lt;em&gt;MUST&lt;/em&gt; follow Node.js ESM package format and have the &lt;code&gt;module&lt;/code&gt; type in its &lt;code&gt;package.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Package &lt;em&gt;MUST&lt;/em&gt; contain valid ESM modules.&lt;/li&gt;
&lt;li&gt;Package &lt;em&gt;MUST NOT&lt;/em&gt; contain any code using features unsupported in the latest stable &lt;code&gt;@babel/preset-env&lt;/code&gt; or &lt;code&gt;core-js&lt;/code&gt;. Usually this means simply not using unfinished proposals.&lt;/li&gt;
&lt;li&gt;Upgrading to a newer major Babel version &lt;em&gt;IS&lt;/em&gt; a breaking change.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Installing a package
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Install the latest stable &lt;code&gt;@babel/preset-env&lt;/code&gt; and &lt;code&gt;core-js&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Install the package.&lt;/li&gt;
&lt;li&gt;At build time compile the package with Babel using &lt;code&gt;@babel/preset-env&lt;/code&gt; preset and load stable polyfills from &lt;code&gt;core-js&lt;/code&gt; (this could be done in preset options setting &lt;code&gt;corejs&lt;/code&gt; and &lt;code&gt;useBuiltIns&lt;/code&gt; props).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Upgrading the package
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Upgrade &lt;code&gt;@babel/preset-env&lt;/code&gt; and &lt;code&gt;core-js&lt;/code&gt; to their latest stable versions.&lt;/li&gt;
&lt;li&gt;Upgrade the package.&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/the-spyke" rel="noopener noreferrer"&gt;
        the-spyke
      &lt;/a&gt; / &lt;a href="https://github.com/the-spyke/rms" rel="noopener noreferrer"&gt;
        rms
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Raw Module Specification is a convention for modern JavaScript packages and modules aiming to avoid excess code recompilation, deoptimization, retaining code readability and ease of debugging.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;If you're interested in this approach and want to help with developing and improving the specification, please join the discussion here or open an issue on GitHub.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;* Photo by &lt;a href="https://unsplash.com/@kelli_mcclintock?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Kelli McClintock&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/box?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>javascript</category>
      <category>node</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Wait, React isn't about virtual DOM?</title>
      <dc:creator>spyke</dc:creator>
      <pubDate>Wed, 28 Oct 2020 15:43:03 +0000</pubDate>
      <link>https://dev.to/spyke/wait-react-isn-t-about-virtual-dom-52b3</link>
      <guid>https://dev.to/spyke/wait-react-isn-t-about-virtual-dom-52b3</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Short answer for those who don't want to read:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Functional programming is the true theme of React. Virtual DOM is just one of its consequences and isn't really a benefit. It is slower by definition, and there could be no DOM at all if you're using React for sound or 3D rendering. Choose React if it matches your mindset, not because it was faster a couple of years ago.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Let's start with the opposite of virtual DOM: the real DOM. We're going to use an uncomplicated Counter component, who's content HTML may look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
  Count: 123
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Increment&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Decrement&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Imaging how would you build it using plain JavaScript. Probably you'll go by one of these 2 ways: &lt;code&gt;createElement&lt;/code&gt; or  &lt;code&gt;innerHTML&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Creating elements manually is time consuming. Just buttons section is almost screen height:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/* rest of the code */&lt;/span&gt;

  &lt;span class="nf"&gt;renderButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleClick&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleClick&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;renderButtons&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buttons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;renderButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Increment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleIncrement&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;renderButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Decrement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleDecrement&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need a &lt;code&gt;createElement&lt;/code&gt; call per every node, to append all required children, etc. But having an element reference allows easy attaching event listeners.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;innerHTML&lt;/code&gt; may look less, but needs ids/classes to assign listeners:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/* rest of the code */&lt;/span&gt;

  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
      &amp;lt;div&amp;gt;
       Count: &amp;lt;span id="label"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/span&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;
       &amp;lt;button type="button" id="btn-inc"&amp;gt;Increment&amp;lt;/button&amp;gt;
       &amp;lt;button type="button" id="btn-dec"&amp;gt;Decrement&amp;lt;/button&amp;gt;
      &amp;lt;div&amp;gt;
    `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;btnIncrement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;btn-inc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;btnDecrement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;btn-dec&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;btnIncrement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleIncrement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;btnDecrement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleDecrement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use less lines on setting attributes, but more on searching for elements for future updates and adding excess classes.&lt;/p&gt;

&lt;p&gt;Of course, no one wants to do such work manually. That's why we've got UI libraries like Angular, Vue, Svelte, and others. These 2 options of building a Counter are roughly what we get in a template-based library.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;innerHTML&lt;/code&gt; is somewhat the original AngularJS: our bundle contains the template string and the engine runs on the client by parsing this template, finding slots for data and expressions inside it, inserting it into the page, and attaching methods as listeners. Larger bundle size and additional load on the browser are downsides of this approach.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;createElement&lt;/code&gt; is like modern Svelte/Ivy, where the template is parsed/compiled build time into a set of document manipulation commands, so no string embedding or runtime is required. We get less bundle overhead and the code is optimized specifically for our component, but at a cost of loosing features on the client.&lt;/p&gt;

&lt;p&gt;Looks not that complicated, right?&lt;/p&gt;

&lt;p&gt;That's because we forgot the part with the template language: conditions and repeaters. All the good stuff anyone can't really use templates without. Imagine adding that to our &lt;code&gt;Counter&lt;/code&gt; code: instead of a simple &lt;code&gt;innerHTML&lt;/code&gt; we need to parse the string and "run" dynamic parts. What if condition changes later, how we're going to find out about that? Will we re-render only dynamic parts or the entire component? The codebase will be complicated and much larger.&lt;/p&gt;

&lt;p&gt;But there's more. What if we need to use a custom Button component?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;
  &lt;span class="na"&gt;component=&lt;/span&gt;&lt;span class="s"&gt;"Button"&lt;/span&gt;
  &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Increment"&lt;/span&gt;
  &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"this.handleIncrement"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's doable. Just create this &lt;code&gt;div&lt;/code&gt; element and pass it as a container to a class registered as &lt;code&gt;Button&lt;/code&gt;. But it must be registered in advance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../components/button.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;UI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Attributes should be parsed to distinguish between &lt;code&gt;div&lt;/code&gt;'s HTML attributes and &lt;code&gt;arguments&lt;/code&gt; to the &lt;code&gt;Button&lt;/code&gt;. Basically the &lt;code&gt;div&lt;/code&gt; is now a sub-tree and should operate on its own.&lt;/p&gt;

&lt;p&gt;But what if we want to use not just a &lt;code&gt;Button&lt;/code&gt;, but one of several components conditionally?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;
  &lt;span class="na"&gt;components=&lt;/span&gt;&lt;span class="s"&gt;"this.isLoading ? 'Button' : 'Image'"&lt;/span&gt;
  &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Increment"&lt;/span&gt;
  &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"this.handleIncrement"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It isn't a simple mapping anymore, but an expression, which needs to be compiled appropriately with JS executed at right times and the component instances destroyed/created. And those attributes may be re-parsed every time, because &lt;code&gt;label&lt;/code&gt; could be an argument for a &lt;code&gt;Button&lt;/code&gt;, but not for an &lt;code&gt;Image&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Think about the original AngularJS with all its scopes, hierarchies, transclusion, etc. Complexity goes nuts with dynamically nested templates. That's why &lt;code&gt;ng-include&lt;/code&gt; was static and we couldn't just render &lt;code&gt;any&lt;/code&gt; template based on business logic.&lt;/p&gt;

&lt;p&gt;But there's more. What if we need to build a component on the fly? Is it even possible, if template parsing and code emitting happens at build time?&lt;/p&gt;

&lt;p&gt;We could get a team of super-stars and try to build an engine or a compiler providing all those features, but the point is that almost every feature influences the rules by which you will write template and/or logic because of it's complexity. And you're still somewhat restricted by a template.&lt;/p&gt;




&lt;p&gt;Now, let's abstract away and get into a &lt;strong&gt;functional data driven land&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Everything in the world could be represented as a result of a function call and its arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function(args) ⟶ anything
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside a function you can do any kind of things including calling other functions (composition). We had functions (methods) before in the &lt;code&gt;Counter&lt;/code&gt; class too, but with different insides.&lt;/p&gt;

&lt;p&gt;Instead of only producing a result, methods alter existing state (in our case document elements with &lt;code&gt;append&lt;/code&gt; or &lt;code&gt;innerHTML&lt;/code&gt;), especially on counter updates. In functional world it is forbidden and passed arguments are immutable. Even if we pass a container &lt;code&gt;div&lt;/code&gt; into a function, it can't &lt;em&gt;add&lt;/em&gt; nodes here. Instead, we should rely only on the returned value. And in case of an update, to re-execute the function and to get next result out of it.&lt;/p&gt;

&lt;p&gt;As we draw a UI, return values should describe it somehow. We could return an &lt;code&gt;HTMLElement&lt;/code&gt;, but it has imperative mutable interface. Anyway, manually using document APIs is time-consuming as we know. Let's revisit HTML of our component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
  Count: 123
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's not that different from a JavaScript object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Count: 123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An object notation is more verbose for sure, as a general language should be to a DSL. But we could easily build such objects ourselves without mutating anything (and parsing a template). We could even reduce boilerplate by implementing a little helper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ui&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Count: 123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Moreover, objects can reference functions, so we don't need a map of pre-registered components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;CounterLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Count is &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;span&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ui&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CounterLabel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the result would be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counterLabelResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Count is &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;span&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ui&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CounterLabel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need somebody to recursively go through this object tree (UI description) calling functions (our components) inside &lt;code&gt;element&lt;/code&gt; properties.&lt;/p&gt;

&lt;p&gt;One more thing. A real-world UI needs to &lt;em&gt;react&lt;/em&gt; on events like button click. How would we know to re-execute the function? Let's just pass a &lt;em&gt;callback&lt;/em&gt; for this, which could be used, for example, as a click handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;FancyButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assume that we've made such function that processes the object tree recursively, simultaneously passing the callback. We will call it &lt;code&gt;getDescriber&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getDescriber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/*
   const describeUI = ...
   ...
  */&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;refresh&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;describeUI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;describer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getDescriber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;describer&lt;/code&gt; accepts a &lt;code&gt;refresh&lt;/code&gt; callback and outputs a complete UI description as a nested object of strings, numbers, and arrays (basically, a JSON).&lt;/p&gt;

&lt;p&gt;The only part missing is a function to read this description and emit DOM elements into the document. We will call it &lt;code&gt;render&lt;/code&gt;, and assume that we have its implementation already done by somebody:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;describer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mountNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;describer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's recap. We have 2 parts and just 3 functions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;element(name, ...children)&lt;/code&gt; and &lt;code&gt;getDescriber(component)&lt;/code&gt; [react]&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;render(describer, mountNode)&lt;/code&gt; [react-dom]&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Part #1 consists of &lt;code&gt;element&lt;/code&gt; and &lt;code&gt;getDescriber&lt;/code&gt; used together to make a description. Part #2 is only &lt;code&gt;render&lt;/code&gt;, which is used exclusively when you need to get actual HTML elements. Both parts are independent. The only thing that connects them together is the &lt;em&gt;structure&lt;/em&gt; of the &lt;em&gt;description&lt;/em&gt;. &lt;code&gt;render&lt;/code&gt; expects a nested object with &lt;code&gt;element&lt;/code&gt; and &lt;code&gt;children&lt;/code&gt; properties. That's all.&lt;/p&gt;

&lt;p&gt;Part #1 could do whatever it wants: generate functions/closures of the fly and execute them, check conditions of any complexity... Instead of adding another complicated template language syntax you just use the whole power of JavaScript. As long as it outputs required objects, no downsides or limits of template engines exist.&lt;/p&gt;

&lt;p&gt;You can call this object description a &lt;code&gt;virtual DOM&lt;/code&gt;, but only if you're using that particular &lt;code&gt;render&lt;/code&gt; function from above. We can make &lt;code&gt;render&lt;/code&gt; that instead of calling &lt;code&gt;document.createElement&lt;/code&gt; will... play sounds! We may interpret the description as we want. Is it DOM anymore?&lt;/p&gt;

&lt;p&gt;As you might guess Part #1 is &lt;code&gt;react&lt;/code&gt; and Part #2 is &lt;code&gt;react-dom&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;React isn't about virtual DOM. It's about abstracting away physical body of your structured data and helping you to update that structure over time. You work on the structure and data with React, some one else will materialize that structure later. Web pages do have a structure, so it's convenient for React to have a materializer for DOM. If Facebook was a music company, maybe React would have shipped with &lt;code&gt;react-midi&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;React is about functional approach, abstraction, flexibility, and unidirectional flow. Virtual DOM is a consequence of using it in a browser. Reconciliation and partial updates aren't fast. Manually crafted set of DOM manipulations is more effective by definition, and compilators can do this for templates. But React allows you to think different about UI, not as about strings and markup. React allows you to use functional composition for UI structure and a real language for UI logic. It's a mindset thing.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building a logger with Undercut</title>
      <dc:creator>spyke</dc:creator>
      <pubDate>Fri, 24 Jul 2020 14:00:59 +0000</pubDate>
      <link>https://dev.to/spyke/building-a-logger-with-undercut-4dg6</link>
      <guid>https://dev.to/spyke/building-a-logger-with-undercut-4dg6</guid>
      <description>&lt;p&gt;In this tutorial we will use &lt;a href="https://undercut.js.org/" rel="noopener noreferrer"&gt;Undercut&lt;/a&gt; to build a non-complicated logger. The simplest one you can think of is just a &lt;code&gt;console.log()&lt;/code&gt;, but we need more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ability to disable logging where not needed without commenting out lines.&lt;/li&gt;
&lt;li&gt;Severity levels support with logger outputting only severe enough entries.&lt;/li&gt;
&lt;li&gt;Log entries processing and transformation (like middlewares).&lt;/li&gt;
&lt;li&gt;Support for custom destinations and outputting into other loggers.&lt;/li&gt;
&lt;li&gt;Less code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://codesandbox.io/s/undercut-logger-nx6dl?fontsize=14&amp;amp;hidenavigation=1&amp;amp;theme=dark" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcodesandbox.io%2Fstatic%2Fimg%2Fplay-codesandbox.svg" alt="Edit Undercut Logger" width="165" height="32"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's start with use case example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;myAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// code&lt;/span&gt;
  &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`User &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; requesting post &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// code&lt;/span&gt;
  &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`Current DB context is`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;dbContext&lt;/span&gt; &lt;span class="p"&gt;}]);&lt;/span&gt;
  &lt;span class="c1"&gt;// code&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Severity levels are embedded into method names, and we use Arrow Functions for building messages (and template literals instead of formatted strings for simplicity). If we need to pass some context among the message, we could do this as a Tuple (an array).&lt;/p&gt;

&lt;p&gt;Of course, we want to have more data than just a message. A log entry should be more robust and could be an object with various meaningful properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;debug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2020-07-23T13:56:19.325Z&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Current DB context is&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Such object could be processed by middlewares, which may add more information to it, format some values, filter excess entries, etc. Basic process may look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[log_entry] =&amp;gt; [middleware_1] =&amp;gt; [middleware_2] =&amp;gt; ... =&amp;gt; [done]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;middleware&lt;/code&gt; could also output an entry somewhere as Browser's console or a remote web server. It should be configurable too.&lt;/p&gt;

&lt;p&gt;As we're going to utilize Undercut for this task, let's think what would be the best choice for our requirements. Log entries come with time. If we can't iterate synchronously, the best option would be to use Observers (Push Lines).&lt;/p&gt;

&lt;p&gt;After slight modifications the diagram from above may look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[log_entry] =&amp;gt; [------------- push_line--------------]
               [operation_1] =&amp;gt; [operation_2] =&amp;gt; ... ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before codding the Logger itself, we need to define severity levels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// level.js&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ERROR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WARNING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;INFO&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DEBUG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will use a factory function approach for the Logger. Start with options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// logger.js_1&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pushLine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toNull&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@undercut/push&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isString&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@undercut/utils&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Level&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./level.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isEnabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isEnabled&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lowestSeverity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;severity&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function requires a &lt;code&gt;pipeline&lt;/code&gt; (a list of operations-middlewares) and an &lt;code&gt;options&lt;/code&gt; object. &lt;code&gt;isEnabled&lt;/code&gt; allows disabling logger entirely, &lt;code&gt;level&lt;/code&gt; defines lowest allowed severity level (all levels lower will be skipped).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isAllowed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;severity&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;severity&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;lowestSeverity&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pushLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;toNull&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;observer&lt;/code&gt; will represent a chain of middlewares. We're using &lt;code&gt;toNull&lt;/code&gt; target because there can be more than 1 destination to write logs and users will specify destinations within the &lt;code&gt;pipeline&lt;/code&gt;, so &lt;code&gt;toNull&lt;/code&gt; is just a placeholder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;messageFactory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isAllowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;messageFactory&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;isString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;log&lt;/code&gt; function represents the whole logic of the Logger. Firstly we need to check severity level as soon as possible to lower performance penalty. Then we call the &lt;code&gt;messageFactory&lt;/code&gt; (an arrow function where you specify the message) and look if it returns a tuple of &lt;code&gt;[message, context]&lt;/code&gt; or just a message string. These values represent initial log entry, which we pass to the &lt;code&gt;observer&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WARNING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;isAllowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;))(&lt;/span&gt;&lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logger object has methods for each severity level. This could be done automatically with a helper like &lt;code&gt;collectProps&lt;/code&gt; and going through the &lt;code&gt;Level&lt;/code&gt; enumeration, but manual way is the simplest one to get type ahead in IDEs.&lt;/p&gt;

&lt;p&gt;Notice the &lt;code&gt;observer&lt;/code&gt; property. It could be used by a middleware to pass entries from one logger to another. The &lt;code&gt;observer&lt;/code&gt; is wrapped into a filter with severity level check. This check is done only in the &lt;code&gt;log&lt;/code&gt; function for performance, so we need to add it here too.&lt;/p&gt;

&lt;p&gt;The Logger is finished, but we need to provide a built-in middleware for connecting loggers together.&lt;/p&gt;

&lt;p&gt;Every middleware is just a Push Operation. Knowing this and we could reuse operations from Undercut instead of writing our own from scratch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@undercut/push&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toLogger&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All &lt;code&gt;toLogger&lt;/code&gt; does is getting that &lt;code&gt;observer&lt;/code&gt; from a logger  logger and passing all incoming log entries to it.&lt;/p&gt;

&lt;p&gt;Let's add more middlewares.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;convertTimestampToISO&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;convertTimestampToISO&lt;/code&gt; maps incoming log entry to a new object (a clone) with &lt;code&gt;timestamp&lt;/code&gt; set to an ISO string instead of original Unix Time number.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filterNoContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;filterNoContext&lt;/code&gt; is even more simple and filters log entries with no &lt;code&gt;context&lt;/code&gt; property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toConsole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;toConsole&lt;/code&gt; outputs every log entry as a JSON string to browser's console adding text prefix at the beginning.&lt;/p&gt;

&lt;p&gt;Testing time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Level&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./level.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createLogger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toLogger&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./logger.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;addLevelName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;addProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;convertTimestampToISO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;filterNoContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;toConsole&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;uppercaseMessage&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./middlewares.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logger1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;convertTimestampToISO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;addLevelName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;addProps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="nf"&gt;toConsole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;logger1: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WARNING&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;logger1&lt;/code&gt; processes only log entries with levels &lt;code&gt;WARNING&lt;/code&gt; and &lt;code&gt;ERROR&lt;/code&gt;. Processing of entries looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create base entry &lt;code&gt;{ severity, timestamp, message, context }&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Convert &lt;code&gt;timestamp&lt;/code&gt; to ISO string.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;level&lt;/code&gt; prop.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;test&lt;/code&gt; prop with value &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Output entry to the console prefixed by "logger1: ".&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We could create another logger and connect it to the prevoius:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logger2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;filterNoContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;toLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logger1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;uppercaseMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;toConsole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;logger2: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;INFO&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;logger2&lt;/code&gt; processes severity levels &lt;code&gt;INFO&lt;/code&gt;, &lt;code&gt;WARN&lt;/code&gt;, &lt;code&gt;ERROR&lt;/code&gt;, only &lt;code&gt;DEBUG&lt;/code&gt; will be skipped.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create base entry &lt;code&gt;{ severity, timestamp, message, context }&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Filter out entry without contexxt.&lt;/li&gt;
&lt;li&gt;Pass entry to &lt;code&gt;logger1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Make message prop uppercase.&lt;/li&gt;
&lt;li&gt;Output entry to the console prefixed by "logger2: ".&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now we can log some stuff:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;logger1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`Debug message: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// No output from logger1 because of low severity.&lt;/span&gt;

&lt;span class="nx"&gt;logger1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`Info message: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// No output from logger1 because of low severity.&lt;/span&gt;

&lt;span class="nx"&gt;logger1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`Warning message: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// logger1: {"severity":1,"timestamp":"2020-07-24T12:34:58.894Z","message":"Warning message: 100","level":"warning","test":true}&lt;/span&gt;

&lt;span class="nx"&gt;logger1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`Error message: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// logger1: {"severity":0,"timestamp":"2020-07-24T12:34:58.895Z","message":"Error message: 101","level":"error","test":true}&lt;/span&gt;

&lt;span class="nx"&gt;logger2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`Info message: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="c1"&gt;// No output from logger1 because of low severity.&lt;/span&gt;
&lt;span class="c1"&gt;// logger2: {"severity":2,"timestamp":1595594098895,"message":"INFO MESSAGE: 102","context":{"username":"root"}}&lt;/span&gt;

&lt;span class="nx"&gt;logger2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`Error message: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// No output from logger1 because was filtered by logger2.&lt;/span&gt;
&lt;span class="c1"&gt;// No output from logger2 because of missing context.&lt;/span&gt;

&lt;span class="nx"&gt;logger2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`Error message: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="c1"&gt;// logger1: {"severity":0,"timestamp":"2020-07-24T12:34:58.895Z","message":"Error message: 104","context":{"username":"root"},"level":"error","test":true}&lt;/span&gt;
&lt;span class="c1"&gt;// logger2: {"severity":0,"timestamp":1595594098895,"message":"ERROR MESSAGE: 104","context":{"username":"root"}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://codesandbox.io/s/undercut-logger-nx6dl?fontsize=14&amp;amp;hidenavigation=1&amp;amp;theme=dark" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcodesandbox.io%2Fstatic%2Fimg%2Fplay-codesandbox.svg" alt="Edit Undercut Logger" width="165" height="32"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it. Try yourself to make some cool middlewares or use the knowledge in other projects. Feel free to ask your questions in comments.&lt;/p&gt;

&lt;p&gt;Visit &lt;a href="https://github.com/the-spyke/undercut" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; Undercut repository for code and &lt;a href="https://undercut.js.org/" rel="noopener noreferrer"&gt;undercut.js.org&lt;/a&gt; website for documentation.&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@neonbrand?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;NeONBRAND&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/mechanic?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>undercutjs</category>
    </item>
    <item>
      <title>What's new in Undercut 0.6.0</title>
      <dc:creator>spyke</dc:creator>
      <pubDate>Wed, 22 Jul 2020 08:18:25 +0000</pubDate>
      <link>https://dev.to/spyke/what-s-new-in-undercut-0-6-0-438e</link>
      <guid>https://dev.to/spyke/what-s-new-in-undercut-0-6-0-438e</guid>
      <description>&lt;p&gt;✂ We continue to add handy utilities into Undercut with release &lt;code&gt;0.6.0&lt;/code&gt; while polishing its API. But this time there are also many internal changes making maintenance easier and users happier.&lt;/p&gt;

&lt;h4&gt;
  
  
  Code coverage
&lt;/h4&gt;

&lt;p&gt;Code coverage is back where it should be above &lt;code&gt;80%&lt;/code&gt; mark, so using Undercut shouldn't be so scary anymore :) In fact, coverage was good from the beginning, but our monorepo structure were interfering with Jest/Istanbul processing. As result, most operation tests weren't considered as hits. &lt;/p&gt;

&lt;h4&gt;
  
  
  Website
&lt;/h4&gt;

&lt;p&gt;Website have moved to &lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt;. In past we were using GitHub Pages for hosting, but it isn't that convenient.&lt;/p&gt;

&lt;p&gt;For example, one of the issues with GHP was the requirement to have full write access on CI as it needs to push files to the &lt;code&gt;gh-pages&lt;/code&gt; branch on build. With Netlify you can continue using read-only keys. On top of that Netlify allows you to have a preview for Pull Requests and even separate deployments for non-main branches.&lt;/p&gt;

&lt;p&gt;The setup was as easy as specifying &lt;code&gt;yarn build&lt;/code&gt; command and a &lt;code&gt;path&lt;/code&gt; to a directory to deploy.&lt;/p&gt;

&lt;h4&gt;
  
  
  Conventional Commits
&lt;/h4&gt;

&lt;p&gt;Some time ago we started to adopt &lt;a href="https://conventionalcommits.org" rel="noopener noreferrer"&gt;Conventional Commits&lt;/a&gt; and &lt;code&gt;0.6.0&lt;/code&gt; release is the first one having its changelog generated from commit messages. Lerna supports Conventional Commits out of the box, which is nice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Release Highlights
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;getObjectType&lt;/code&gt; for retrieving more detailed type name like &lt;code&gt;AsyncFunction&lt;/code&gt; from Object's &lt;code&gt;toString&lt;/code&gt; method.&lt;/li&gt;
&lt;li&gt;Classic helpers in a form of &lt;code&gt;head/tail&lt;/code&gt; utilities for working with Iterables. Unfortunately, the &lt;code&gt;head&lt;/code&gt; utility have replaced former &lt;code&gt;peekIterable&lt;/code&gt;, so we have a &lt;code&gt;breaking change&lt;/code&gt; here.&lt;/li&gt;
&lt;li&gt;A pack of utilities to quickly filter/map/collect object keys and values. You were able to do this with Pull already, but sometimes you only want a short single operation instead of the full power and ceremony of the pipelines.&lt;/li&gt;
&lt;li&gt;A set of randomized functions like &lt;code&gt;randomDecimal&lt;/code&gt; or &lt;code&gt;randomIndex&lt;/code&gt; utilities.&lt;/li&gt;
&lt;li&gt;Somehow missing &lt;code&gt;isPromise&lt;/code&gt; utility.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lots of other code and documentation improvements. Check out &lt;a href="https://github.com/the-spyke/undercut/compare/v0.5.1...v0.6.0" rel="noopener noreferrer"&gt;the list commits from previous version&lt;/a&gt; or view our &lt;a href="https://github.com/the-spyke/undercut/releases/tag/v0.6.0" rel="noopener noreferrer"&gt;full release notes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codesandbox.io/s/undercut-demo-1up46?fontsize=14&amp;amp;hidenavigation=1&amp;amp;moduleview=1&amp;amp;theme=dark&amp;amp;previewwindow=console" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcodesandbox.io%2Fstatic%2Fimg%2Fplay-codesandbox.svg" alt="Undercut Demo" width="165" height="32"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Visit &lt;a href="https://github.com/the-spyke/undercut" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; for code and &lt;a href="https://undercut.js.org/" rel="noopener noreferrer"&gt;undercut.js.org&lt;/a&gt; for documentation.&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@floriancario?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Florian Cario&lt;/a&gt; on &lt;a href="https://unsplash.com/t/textures-patterns?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>news</category>
      <category>undercutjs</category>
    </item>
    <item>
      <title>Reusing operations for cleaner code</title>
      <dc:creator>spyke</dc:creator>
      <pubDate>Tue, 12 May 2020 14:57:55 +0000</pubDate>
      <link>https://dev.to/spyke/reusing-operations-for-cleaner-code-1159</link>
      <guid>https://dev.to/spyke/reusing-operations-for-cleaner-code-1159</guid>
      <description>&lt;p&gt;Imagine we're getting a list of users from an API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getUsers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// [{ id, name, location, ... }, ...]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can do quite a complex processing using &lt;a href="https://github.com/the-spyke/undercut" rel="noopener noreferrer"&gt;Undercut&lt;/a&gt; pipelines, but what if we need only to do something quick: like grouping users by a location and just passing the result further? Even calling &lt;code&gt;pull&lt;/code&gt; may be too much:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;sendPreview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;pullArray&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A case with a variable may be distracting too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usersByLocation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pullArray&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;sendPreview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usersByLocation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fortunately, Undercut operations are framework-agnostic, which means they work by themselves. An operation is just a functions doing something on an Iterables. Arrays are Iterables, so our &lt;code&gt;users&lt;/code&gt; fit just right and &lt;code&gt;groupBy(u =&amp;gt; u.location)(users)&lt;/code&gt; will return grouped users:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;sendPreview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it's better as we have no noise like &lt;code&gt;pullArray&lt;/code&gt; and square brackets.&lt;/p&gt;

&lt;p&gt;There's another use case: you can define selectors using operations like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;groupUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// And use later:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usersByLocation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;groupUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have specifically chosen &lt;code&gt;groupBy&lt;/code&gt; because with even shorter actions you will likely go with plain Array methods and Arrow Functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if you do need more serious job like &lt;code&gt;flatMap&lt;/code&gt;/&lt;code&gt;groupBy&lt;/code&gt;/&lt;code&gt;orderBy&lt;/code&gt;/etc or already using Undercut in other places, consider reusing from the Undercut or your own custom operations.&lt;/p&gt;

&lt;p&gt;Undercut docs: &lt;a href="https://undercut.js.org" rel="noopener noreferrer"&gt;undercut.js.org&lt;/a&gt;&lt;br&gt;
Previous post: &lt;a href="https://dev.to/the_spyke/what-is-undercut-js-3ih1"&gt;Lazy data processing using Undercut&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>webdev</category>
      <category>undercutjs</category>
    </item>
    <item>
      <title>Pitfalls of Flux Dispatcher</title>
      <dc:creator>spyke</dc:creator>
      <pubDate>Fri, 07 Feb 2020 13:47:18 +0000</pubDate>
      <link>https://dev.to/spyke/pitfalls-of-flux-dispatcher-715</link>
      <guid>https://dev.to/spyke/pitfalls-of-flux-dispatcher-715</guid>
      <description>&lt;p&gt;&lt;a href="https://facebook.github.io/flux/" rel="noopener noreferrer"&gt;Flux&lt;/a&gt; was presented in May 2014 and quickly become a new movement in web development. Today Flux isn't that widely used. The driver seat was taken by its offspring Redux. Anyway, it's still interesting to discuss some of the issues with Flux's architecture about which you don't even think in Redux.&lt;/p&gt;

&lt;p&gt;This one was famous:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Cannot dispatch in the middle of a dispatch"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This error had to mean that you did &lt;code&gt;dispatch()&lt;/code&gt; in a wrong time and need to move it somewhere else. The most brave people just ignored it by wrapping the dispatch into &lt;code&gt;setTimeout()&lt;/code&gt;. But there were many other hacks to avoid it.&lt;/p&gt;

&lt;p&gt;Flux's official website and issue tracker have no good explanation of how to deal with this problem only recommending not to dispatch. Unfortunately, there're too many scenarios when it's unavoidable. As you'll see later this error is only a symptom of a much larger issue.&lt;/p&gt;

&lt;p&gt;Flux describes a store as a state manager of a &lt;em&gt;domain&lt;/em&gt;. That means you will have more stores than 1. Same time, some stores may depend on another, what is described by calling &lt;code&gt;waitFor()&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Imagine a basic app of two components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Posts&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The App is the root and shows a login screen instead of its children while user isn't authenticated. The Posts component starts loading its data in &lt;code&gt;componentDidMount()&lt;/code&gt; hook what is the recommended practice. Both these components depend on different stores: &lt;code&gt;AppStore&lt;/code&gt; and &lt;code&gt;PostsStore&lt;/code&gt;. The &lt;code&gt;PostsStore&lt;/code&gt; may also depend on the &lt;code&gt;AppStore&lt;/code&gt; too, but it isn't important.&lt;/p&gt;

&lt;p&gt;Let's look at the time when user just authenticated and the app got a positive answer from the server with user's session:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F40nzkj6abib4psl3w8bn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F40nzkj6abib4psl3w8bn.png" alt="Auth Flow Diagram" width="701" height="701"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Actions are represented as arrow-like blocks. Let's follow the diagram:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;AUTH_SUCCESS&lt;/code&gt; is dispatched. Flux Dispatcher starts calling stores' callbacks and does this in order.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AppStore&lt;/code&gt;'s callback is called first, the store recalculates its state.&lt;/li&gt;
&lt;li&gt;All &lt;code&gt;AppStore&lt;/code&gt; subscribers start to update. We have only one subscriber in our case  -- the &lt;code&gt; App&lt;/code&gt; component.&lt;/li&gt;
&lt;li&gt;The state was updated, and the &lt;code&gt;App&lt;/code&gt; starts to re-render.&lt;/li&gt;
&lt;li&gt;This time &lt;code&gt;isAuth&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt; and we start rendering &lt;code&gt;Posts&lt;/code&gt; (this happens synchronously).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;componentDidMount()&lt;/code&gt; also happens synchronously. So, just right after the initial &lt;code&gt;Posts&lt;/code&gt; render we start loading actual posts (&lt;code&gt;Posts&lt;/code&gt; shows a &lt;code&gt;Loader&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Loading posts means dispatching &lt;code&gt;LOAD_POSTS_STARTED&lt;/code&gt; first.&lt;/li&gt;
&lt;li&gt;What means we're back in the Flux Dispatcher, which will throw the nasty error.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now look at the &lt;code&gt;#5&lt;/code&gt;. When render happens we're still in the middle of dispatch. That means that only a part of stores were updated and we're looking at &lt;strong&gt;inconsistent state&lt;/strong&gt;. Not only we're getting errors in totally normal scenarios, but even without errors the situation is hardly better.&lt;/p&gt;

&lt;p&gt;The most popular solution to this entire scope of issues is to fire change event in &lt;code&gt;setTimeout()&lt;/code&gt;. But this removes synchronicity of React rendering. Theoretically, event subscribers may be called in different order, because order of execution of &lt;code&gt;setTimeout&lt;/code&gt; callbacks is unspecified (even if we know that browsers just add them to a queue).&lt;/p&gt;

&lt;p&gt;I like another solution which isn't that well known, but lies on surface. Redux works this way and is consistent, error-less, and synchronous. The whole dispatch process inside Redux may be written as such:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$reducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$emit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It calculates the new state and only then calls subscribers. The state is always consistent, and the entire process is like an atomic DB transaction.&lt;/p&gt;

&lt;p&gt;In Flux this approach would be more verbose, but still doable. Stores manage their subscribers individually, but they could return a function to dispatcher. This function will call store's &lt;code&gt;emit()&lt;/code&gt;. Most of the time stores don't pass event arguments, so they would just return the &lt;code&gt;emit&lt;/code&gt; itself. In case if you want to optimize some things and filter events based on args, a store may return a custom callback.&lt;/p&gt;

&lt;p&gt;Taking Flux Dispatcher as the base only a few places require tweaks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="c1"&gt;// No more "Cannot dispatch..."&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_startDispatching&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Same try/finally as before.&lt;/span&gt;
    &lt;span class="c1"&gt;// After state calculation notify all subscribers.&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_notifyAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;_notifyAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// In case of a nested dispatch just ignore.&lt;/span&gt;
    &lt;span class="c1"&gt;// The topmost call will handle all notifications.&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_isNotifying&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_isNotifying&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_notifyQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notify&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_notifyQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_isNotifying&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;_invokeCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_isPending&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// Save callback from the store to the queue.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notify&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_callbacks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_pendingPayload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_notifyQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_isHandled&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It requires some error handling code, but the idea should be clear. This is how a store may look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostsStore&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;EventEmitter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dispatcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$emit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$emit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatchToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dispatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actionType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;LOAD_POSTS_SUCCESS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="c1"&gt;// Don't forget to "return" here&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$loadPosts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;$loadPosts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$posts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$emit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// A generic case with no args;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;$clearPosts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$posts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="c1"&gt;// When only a part of subscribers should update.&lt;/span&gt;
        &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rest of the app's code stays the same.&lt;/p&gt;

&lt;p&gt;This solution has not that big refactoring penalty, but gives you state consistency, removes unnecessary errors, and keeps update-render process synchronous and simple to follow.&lt;/p&gt;

&lt;p&gt;Atomicity is a nice property which we hadn't in Flux and not always notice in Redux. Redux is also more simple, maybe that's why we (the community) haven't seen implementations like Atomic Flux Dispatcher and moved forward straight to Redux.&lt;/p&gt;

&lt;p&gt;Originally posted on &lt;a href="https://medium.com/@spyke/pitfalls-of-flux-dispatcher-7ac41dd05c72" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; in 2019.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>redux</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Async Redux doesn't exist</title>
      <dc:creator>spyke</dc:creator>
      <pubDate>Wed, 05 Feb 2020 08:32:53 +0000</pubDate>
      <link>https://dev.to/spyke/async-redux-doesn-t-exist-2fnd</link>
      <guid>https://dev.to/spyke/async-redux-doesn-t-exist-2fnd</guid>
      <description>&lt;p&gt;Sometimes people ask what is the best way to handle asynchronicity in &lt;a href="https://redux.js.org" rel="noopener noreferrer"&gt;Redux&lt;/a&gt;? There is official documentation about it, but I suggest revisiting some basic concepts to see if it's really that simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basics
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;state&lt;/code&gt; is an object. It's used as a value somewhere on UI or for its rendering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zerocool&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An &lt;code&gt;action&lt;/code&gt; is an object too. It describes an event (&lt;a href="https://www.youtube.com/watch?v=STKCRSUsyP0" rel="noopener noreferrer"&gt;or a command&lt;/a&gt;) happened in app's world. By convention it must have the "type" property containing event name and may have some other data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ADD_TODO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;reducer&lt;/code&gt; is a function. Its signature is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following example has a function with similar signature and even a comparable method name "reduce":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In fact, this is exactly what happens in Redux, but instead of an array of numbers Redux gets an infinite array (stream) of events (actions), and its reduction spans the life-time of the app. Of course, &lt;code&gt;state&lt;/code&gt; and &lt;code&gt;action&lt;/code&gt; could be primitive types in Redux too, but in real world apps it isn't super useful.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;reducer&lt;/code&gt; is all about computation. Nothing more, nothing less. It is synchronous, pure, and simple like a sum.&lt;/p&gt;

&lt;p&gt;Developers use Redux through a &lt;code&gt;store&lt;/code&gt;. It is an object that remembers the computation (reducer) and its first argument (state) freeing you from passing it every time. Interactions are based on calling &lt;code&gt;dispatch()&lt;/code&gt; method to run the computation and accessing the last computed value by calling &lt;code&gt;getState()&lt;/code&gt;. Parameter types are irrelevant to &lt;code&gt;dispatch()&lt;/code&gt; because it simply passes them to reducer, &lt;code&gt;dispatch()&lt;/code&gt; doesn't return a value either. This is how a simple Redux store may look and work like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Instead of manually implementing store subscriptions we could use EventEmitter.&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Store&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;EventEmitter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// This is the only thing happening inside a store.&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Let's try the store on numbers.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// And output its state to the console on every dispatch.&lt;/span&gt;
&lt;span class="c1"&gt;// "on()" is similar to "subscribe()" in the Redux and comes from EventEmitter.&lt;/span&gt;
&lt;span class="nx"&gt;store1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;store1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// 1&lt;/span&gt;
&lt;span class="c1"&gt;// 3&lt;/span&gt;
&lt;span class="c1"&gt;// 6&lt;/span&gt;

&lt;span class="c1"&gt;// Now let's try a more real-world reducer.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ADD_ITEM&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]),&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;

&lt;span class="c1"&gt;// Outputting the state as a JSON.&lt;/span&gt;
&lt;span class="nx"&gt;store2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;())));&lt;/span&gt;

&lt;span class="nx"&gt;store2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ADD_ITEM&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// {"items":["Hello"]}&lt;/span&gt;
&lt;span class="nx"&gt;store2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ADD_ITEM&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;World&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// {"items":["Hello","World"]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks &lt;a href="https://en.wikipedia.org/wiki/KISS_principle" rel="noopener noreferrer"&gt;KISS&lt;/a&gt;ish and complies with the &lt;a href="https://en.wikipedia.org/wiki/Single_responsibility_principle" rel="noopener noreferrer"&gt;Single responsibility principle&lt;/a&gt;. The example is so simple that it's hard to imagine where to put asynchronicity into. As you will see later, attempts to add asynchronicity will break some of the definitions written above.&lt;/p&gt;

&lt;p&gt;By the way, the original Redux isn't that small. Why? Because it provides various utilities: middlewares, store enhancement, etc. More on this later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Asynchronicity
&lt;/h2&gt;

&lt;p&gt;If you try to read Redux docs about asynchronicity, the first page you'll encounter is the &lt;a href="https://redux.js.org/advanced/async-actions" rel="noopener noreferrer"&gt;Async Actions&lt;/a&gt; page. Its title looks rather strange because we know that actions are objects and objects can't be async. Reading further down you see &lt;a href="https://redux.js.org/advanced/async-actions#async-action-creators" rel="noopener noreferrer"&gt;Async Action Creators&lt;/a&gt; and middlewares for them.&lt;/p&gt;

&lt;p&gt;Let's look at what are regular synchronous Action Creators first. From the docs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Action creators are exactly that - functions that create actions.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;addTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ADD_TODO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;text&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;addTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Finish the article&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A factory function for reducing code duplication in creating action objects, cool. If there're dispatches of same actions in different parts of the app, Action Creators may help.&lt;/p&gt;

&lt;p&gt;Middlewares. They are utilities to override store's behavior in more functional style (like &lt;a href="https://en.wikipedia.org/wiki/Decorator_pattern" rel="noopener noreferrer"&gt;Decorators&lt;/a&gt; in OOP). So, you don't have to write this by hand if you want to log every dispatched action to the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;originalDispatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;myCustomDispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`action : &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;originalDispatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In reality it looks more like a chain of dispatch functions calling each other in order with the original one in the end. But the idea is similar. Async Action Creators require specific middlewares to work, let's check them out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redux Thunk
&lt;/h2&gt;

&lt;p&gt;The first one on the list is &lt;a href="https://github.com/gaearon/redux-thunk" rel="noopener noreferrer"&gt;redux-thunk&lt;/a&gt;. This is how a thunk may look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;addTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;dispatch&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;callWebApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ADD_TODO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sendEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;addTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Finish the article&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the description of the library:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Redux Thunk middleware allows you to write action creators that return a function instead of an action.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Returning a function from Action Creators? Actions Creators create actions (objects), it's obvious from their name. There should be a new term instead.&lt;/p&gt;

&lt;p&gt;Google says that by returning functions you may continue to dispatch normally and components will not depend on Action Creators' implementation. But dispatching "normally" means running the computation of the new state and doing it synchronously. With this new "normal" dispatch you can't check &lt;code&gt;getState()&lt;/code&gt; to see the changes right after the call, so the behavior is different. It's like patching &lt;code&gt;Lodash.flatten()&lt;/code&gt; to allow you to continue "normally" flattening Promises instead of Arrays. Action Creators return objects, so there's no implementation either. Same time, presentational components don't usually know about &lt;code&gt;dispatch()&lt;/code&gt;, they operate with available handlers (passed as React props). Buttons are generic. It's Todo page who decides what a button does, and this decision is specified by passing the right &lt;code&gt;onClick&lt;/code&gt; handler.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A &lt;code&gt;dispatch()&lt;/code&gt; is a function call, just like &lt;code&gt;sum()&lt;/code&gt;. How to delay &lt;code&gt;sum()&lt;/code&gt; in JavaScript? By using &lt;code&gt;setTimeout()&lt;/code&gt;. How to delay a button click? With &lt;code&gt;setTimeout()&lt;/code&gt;, but inside a handler. It is unlikely that patching a button to know how to delay clicks (if it is not a button animating delay countdown, which is different) is necessary. How to call a function if certain conditions are met? By adding an "if-then-else" block inside a handler. Plain JS.&lt;/p&gt;

&lt;p&gt;Looking closer at the proposed dispatch call itself. Not only it changes dispatch's interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But we're passing a function expecting &lt;em&gt;dispatch&lt;/em&gt; as an argument into a function called &lt;em&gt;dispatch&lt;/em&gt;. This is quite confusing 🤷‍♂️ Melding together different concepts removes simplicity and raises contradictions. But what is the problem that Redux Thunk is trying to solve in the first place?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleAddTodo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;addTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleAddTodo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Add&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding some async calls turns into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleAddTodo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;callWebApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;addTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleAddTodo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Add&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing has changed for the button, but there is a problem indeed if you have several identical &lt;code&gt;handleAddTodo()&lt;/code&gt; implementations in different parts of the app. Cutting corners with Redux Thunk may look like a solution, but still will add all downsides this middleware introduce. It can be avoided by having only one implementation somewhere on upper level and passing it down or by extracting &lt;code&gt;dispatch()&lt;/code&gt; calls into external functions (basically moving &lt;code&gt;handleAddTodo()&lt;/code&gt; to another file).&lt;/p&gt;

&lt;h2&gt;
  
  
  Redux Promise
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/redux-utilities/redux-promise" rel="noopener noreferrer"&gt;Redux Promise&lt;/a&gt; encourages you to dispatch Promises. It is very similar by effect to Redux Thunk, so I'll skip it.&lt;/p&gt;

&lt;p&gt;There is also another way encouraged by subsequent middlewares, but let's step aside from thunks and asynchronicity for a second and talk about processes happening inside apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Business Logic
&lt;/h2&gt;

&lt;p&gt;Apps react on users and environment. Complexity of reactions grows with app's complexity. Instead of simple things like changing button's color on a click, apps starts to execute rather complex scenarios. For example, adding a Todo record to the state is simple. Adding it also to the local storage, syncing it to a backend, showing a notification on the screen… is not so. Somewhere between those steps may be even a user interaction.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Foescg1jdcn5iyb53pec4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Foescg1jdcn5iyb53pec4.png" alt="Flow chart example" width="800" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Such groups of actions are usually represented by flow charts and have many names: flows, workflows, control flows, business processes, pipelines, scenarios, sagas, epics, etc. I will use the term "workflow". A simple money transfer between two bank accounts internally may be a huge operation involving distributed transactions between multiple independent parties. But the workflow from the image above may be a simple function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;addTodoWorkflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;addTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nf"&gt;saveToLocalStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isSignedIn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;syncWithServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;OK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;showSuccess&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;todoSynced&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;showError&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks like and totally is a regular function composition. I made it sync, but it will be the same with promises.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;From the point of workflow's view dispatch(), syncWithServer(), and Lodash.groupBy() are the same.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Browser APIs, web clients, libraries, triggering UI changes, coming from imports or arriving in arguments, sync or async. They all are just some services that were composed into a workflow to do the job. Even if a workflow is asynchronous, you still run it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;addTodoWorkflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;...);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have a button submitting a Todo, just call it in the event handler. In more advanced scenarios you will have tons of async stuff, cancellation, progress reporting, etc. Achieving this is possible with extended promises, generators, streams, and other libraries and techniques (such as &lt;a href="https://rxjs.dev/" rel="noopener noreferrer"&gt;reactive programming&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Workflows exist is many areas of software development, and they aren't tied to UI state management. They may also call dispatch() several times with completely different action types or not to have UI indication and state change at all. Workflows may be composable just like functions in JS. Similar concepts exist even &lt;a href="https://aws.amazon.com/step-functions/use-cases/" rel="noopener noreferrer"&gt;high in the clouds&lt;/a&gt; and in &lt;a href="https://nodered.org/" rel="noopener noreferrer"&gt;IoT&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Understanding that workflows are a separate concern is important. By moving business logic into Action Creators this separation starts to vanish. Redux doesn't require special treatment, nor it is more important than other subsystems in the app.&lt;/p&gt;

&lt;p&gt;There two ways of executing workflows: directly and indirectly.&lt;/p&gt;

&lt;p&gt;The direct way is the simplest: you call the workflow directly in a handler. This way you have a good visibility of what will happen and control right in the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onAddTodoClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;addTodoWorkflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The indirect way is opposite. You start with a dummy action like &lt;code&gt;ADD_TODO&lt;/code&gt; that must not change any state, but there is another system subscribed to Redux actions. This system will launch a workflow defined for this specific action. This way you may add functionality without updating UI components' code. But now you have no idea what will happen after a dispatch. Let's look on the middlewares.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redux Saga
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/redux-saga/redux-saga" rel="noopener noreferrer"&gt;Redux Saga&lt;/a&gt; isn't really about the Saga pattern.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Distributed Saga pattern is a pattern for managing failures, where each action has a compensating action for rollback.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It doesn't help you dealing with state rollbacks. Instead it allows you to write workflows in a &lt;a href="https://github.com/ubolonton/js-csp" rel="noopener noreferrer"&gt;CSP&lt;/a&gt;-style manner, but with power of generators (which is great). There're very few mentions of Redux in the docs. 99% of Redux Saga are about sagas themselves hidden in sub-packages.&lt;/p&gt;

&lt;p&gt;Sagas are pure workflows, and the docs teach you to manage running tasks, doing effects, and handling errors. The Redux part only defines a middleware which will &lt;a href="https://github.com/redux-saga/redux-saga/blob/master/packages/core/src/internal/middleware.js#L28" rel="noopener noreferrer"&gt;repost actions&lt;/a&gt; to the root saga. Instead of manually building a map &lt;code&gt;[Action → Saga]&lt;/code&gt; you need to compose all sagas into a tree similar to reducers composition in Redux. UI code remains the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;addTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ADD_TODO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;text&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleAddTodo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;addTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleAddTodo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Add&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Changes happen only in the corresponding saga:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;addTodoSaga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;takeEvery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ADD_TODO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;webApi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ADD_TODO_SUCCEEDED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;rootSaga&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="p"&gt;...,&lt;/span&gt;
      &lt;span class="nf"&gt;addTodoSaga&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is dramatically different to Redux Thunk: the &lt;code&gt;dispatch()&lt;/code&gt; hasn't changed, Action Creators stay sync and sane, Redux continues to be simple and clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redux Observable
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/redux-observable/redux-observable" rel="noopener noreferrer"&gt;Redux Observable&lt;/a&gt; is identical to Redux Sagas, but instead of CSP and Sagas you work with Observables and Epics leveraging RxJS (more difficult, but even more powerful).&lt;/p&gt;

&lt;h2&gt;
  
  
  Retrospective
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;So, what is the best way to handle asynchronicity in Redux?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is no asynchronicity in Redux. You should not build a facade with middlewares like Thunk hiding the real Redux behind it. It &lt;a href="https://en.wikipedia.org/wiki/Coupling_%28computer_programming%29" rel="noopener noreferrer"&gt;couples&lt;/a&gt; the knowledge of workflow execution with UI state management and makes the terminology complicated.&lt;/p&gt;

&lt;p&gt;There are ways to react on actions in a better manner. You may choose a direct approach of calling workflows manually and/or going by indirect path of binding workflows to actions. Both ways have their own strengths and weaknesses.&lt;/p&gt;

&lt;p&gt;Sagas provide a nice balance in ease of use, functionality, testability and may be a good starting point. Same time, choosing Sagas over calling workflows directly is like choosing between Redux and React State: you don't always need the former.&lt;/p&gt;

&lt;p&gt;In advanced scenarios with async modules you may want to register new sagas/epics on demand instead of a prebuilt root saga/epic. But usually it's better not to overthink.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally posted on &lt;a href="https://medium.com/@spyke/overthinking-async-redux-6bce9a21c32" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; in 2019.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>redux</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Lazy data processing using Undercut</title>
      <dc:creator>spyke</dc:creator>
      <pubDate>Mon, 20 Jan 2020 12:31:42 +0000</pubDate>
      <link>https://dev.to/spyke/what-is-undercut-js-3ih1</link>
      <guid>https://dev.to/spyke/what-is-undercut-js-3ih1</guid>
      <description>&lt;p&gt;&lt;a href="https://undercut.js.org" rel="noopener noreferrer"&gt;Undercut&lt;/a&gt; is a JavaScript library for processing data in a lazy or deferred manner by building pipelines.&lt;/p&gt;

&lt;p&gt;The focus of the library is on leveraging existing JavaScript features like Iterators/Generators while having balanced API: not being Java/C# influenced or heavily functional. &lt;code&gt;Undercut&lt;/code&gt; also aims to avoid prototype extension and a situation, where you need to name a method as &lt;code&gt;flat&lt;/code&gt; instead of &lt;code&gt;flatten&lt;/code&gt;. You may also use it as an alternative to Lodash's &lt;code&gt;chain&lt;/code&gt; functionality with support for lazy execution, tree shaking, etc.&lt;/p&gt;

&lt;p&gt;Imagine a conveyor on a car factory: a chain of operations from welding body parts and painting doors to gluing a logo and inflating wheels. Every operation is independent and is based only on a protocol: a car comes from this side and goes to that side after the operation is complete.&lt;/p&gt;

&lt;p&gt;In JavaScript we may represent this as an array of functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;skip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;take&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, those operations have some input data: this car should have 17" wheels, that car should have 16" wheels. We can do this too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;skip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calling &lt;code&gt;skip(1)&lt;/code&gt; creates a function (operation) that knows how to skip exactly 1 item (car).&lt;/p&gt;

&lt;p&gt;Sometimes you need to make a new model with additional equipment package. It may be as simple as adding a couple of steps to the conveyor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pipeline_2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or replacing some steps in existing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Arrays give you this flexibility to concatenate, merge, copy, and modify existing pipelines.&lt;/p&gt;

&lt;p&gt;To finish the conveyor there should be some mechanism like moving belt that will transport a car from one operation from another. This is where &lt;code&gt;Undercut&lt;/code&gt; tries to help (not mentioning a pack of 40+ prebuilt common operations like filter/map/skip/etc).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://undercut.js.org/docs/pull/core-functions" rel="noopener noreferrer"&gt;Core pull functions&lt;/a&gt; allow you to quickly run a pipeline and acquire the result or combine it into something self-contained and reusable like an Iterable.&lt;/p&gt;

&lt;p&gt;Having a list of numbers called &lt;code&gt;source&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a &lt;code&gt;pipeline&lt;/code&gt; of operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;skip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could &lt;code&gt;pull&lt;/code&gt; items out of the &lt;code&gt;source&lt;/code&gt; through the &lt;code&gt;pipeline&lt;/code&gt; and get an array of result items:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pullArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our case &lt;code&gt;result&lt;/code&gt; will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All is done lazily, so &lt;code&gt;map&lt;/code&gt; won't run for the skipped item. There're also &lt;code&gt;pullValue&lt;/code&gt;, if your result is a single value (not a sequence). Or more generic &lt;code&gt;pull&lt;/code&gt;, where you pass &lt;code&gt;target&lt;/code&gt; function getting result items and converting it into whatever you want.&lt;/p&gt;

&lt;p&gt;As &lt;code&gt;pull&lt;/code&gt; is built around Iterables, and many native objects are Iterable out of the box (arrays, strings, maps, sets, etc), you can easily transform a Map of Usernames-by-Id into an Object of Ids-by-Username.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;namesById&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sam&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;kate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1004&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]]),&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idsByNameObj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;namesById&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// idsByNameObj == Object {"1000":"sam","1004":"kate"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Moreover, you may create a reusable &lt;code&gt;view&lt;/code&gt; of this data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idsByName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pullLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;pullLine&lt;/code&gt; function binds together a &lt;code&gt;pipeline&lt;/code&gt; and a &lt;code&gt;source&lt;/code&gt; into an Iterable. Every time you iterate over it, the pipeline will be executed again, giving you a fresh view on processed data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;namesById&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sam&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1111&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idsByName&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Object {"1111":"sam","1004":"kate"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every operation is just a function, so you can create your own. Or even create a whole library of your own operations and reuse in different projects. The protocol, operations rely on, is similar to &lt;code&gt;car-in/car-out&lt;/code&gt;, but instead of cars there're Iterables. An operation get an Iterable of items to process and return an Iterable of processed items. Returning an Iterable sounds complicated, but it isn't with JavaScript Generators.&lt;/p&gt;

&lt;p&gt;Let's build a &lt;code&gt;pow&lt;/code&gt; operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;powOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iterable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;iterable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;exponent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nx"&gt;newItem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get an Iterable, go by its items, calculate new values, put them into another iterable with &lt;code&gt;yield&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you aren't familiar with generators (functions marked with &lt;code&gt;*&lt;/code&gt; asterisk). Basically, the return value of such function will be not what you return, but an implicit Iterable you can put items into with the &lt;code&gt;yield&lt;/code&gt; keyword. Please read &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols" rel="noopener noreferrer"&gt;MDN&lt;/a&gt; for more detailed decsription. I also recommend reading an awesome book &lt;a href="https://exploringjs.com/es6.html" rel="noopener noreferrer"&gt;Exploring ES6&lt;/a&gt; by Dr. Axel Rauschmayer.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Actually, one important aspect is missing. The &lt;code&gt;exponent&lt;/code&gt; value isn't defined and should be assigned in a pipeline like those 17" wheels. To fix this just add another function around:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exponent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;powOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iterable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;iterable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;exponent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nx"&gt;newItem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this &lt;code&gt;pow&lt;/code&gt; we can actually use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pullArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// [1, 4, 9]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It was only a brief review of the &lt;code&gt;Undercut&lt;/code&gt;, but should be enough for basic use cases. If you want to learn more, please with &lt;a href="https://undercut.js.org" rel="noopener noreferrer"&gt;undercut.js.org&lt;/a&gt; for documentation and tutorials.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>tutorial</category>
      <category>undercutjs</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
