Category: JavaScript

  • async/await and error handling

    async/await and error handling

    Async/await is the simplest, most readable way in modern JavaScript to work with asynchronous code. We are going to talk through why to use it, how it works, common errors, tooling and comparisons to other async patterns.

    Why use async/await

    • Readability: async/await turns nested promise chains and callback hell into linear-looking code that’s easier to understand
    • Error flow: try/catch works naturally with awaits, so error handling is familiar
    • Composability: works well with Promise utilities (Promise.all, Promise.race) for concurrency control
    • Adoption: native in Node.js and modern browsers, supported by TypeScript, and easy to transpile

    async/await basics

    • Mark a function async to allow using await inside it:
      • async function foo() { // }
    • Await pauses the async function until the awaited Promise settles:
      • const result = await fetch(url)
    • Await unwraps fulfilled values; if the Promise rejects, await throws the rejection reason as an exception that can be caught with try/catch

    Basic examples

    1. Fetch API (browser)
    
    
    
    
    
    async function getUser(id) {
      try {
        const res = await fetch(`/api/users/${id}`);
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        const user = await res.json();
        return user;
      } catch (err) {
        // handle or rethrow
        console.error('getUser failed', err);
        throw err;
      }
    }
    
    1. Node/DB example
    
    
    
    
    
    async function createOrder(orderData) {
      try {
        await db.beginTransaction();
        const order = await db.insert('orders', orderData);
        await db.commit();
        return order;
      } catch (err) {
        await db.rollback();
        throw err;
      }
    }
    

    Error handling patterns

    1. Local try/catch (simple)
    • Use try/catch around code that might throw
    • Good for single logical operation or when you need to handle the error locally
    1. Bubble up (rethrow)
    • Catch to perform cleanup/logging, then rethrow to let callers decide
    
    
    
    
    
    try {
      // ...
    } catch (err) {
      log(err);
      throw err; // preserve stack
    }
    
    1. Return error objects (functional)
    • Avoid exceptions by returning { value, error } or Result types — good for predictable flows and avoiding exceptions across module boundaries
    
    
    
    
    
    
    
    
    
    
    async function safeGet(url) {
      try {
        return { value: await fetchJson(url), error: null };
      } catch (error) {
        return { value: null, error };
      }
    }
    
    1. Top-level centralized handlers
    • In servers or UI apps, have a top-level error handler to convert uncaught errors into responses or UI notifications
    • Examples: Express error middleware, window.onerror, React Error Boundaries (for render errors — async in event handlers still needs try/catch)
    1. Promise utilities + aggregated errors
    • Promise.all rejects fast on the first rejection; Promise.allSettled shows all results; Promise.any resolves on first success or rejects with AggregateError
    • Use these depending on whether you prefer fail-fast or collecting partial results

    Common problems and what to look for

    • Not awaiting a Promise: forget to use await leads to unhandled rejections or unexpected behavior
      • Example: const data = fetch(url); // data is Promise not resolved value
    • Mixing callback-style error handling with async/await incorrectly
    • UnhandledPromiseRejection: Node will warn; use global handlers in dev to catch
    • Blocking the event loop: await long-running CPU work — use worker threads or offload computation
    • Relying on sequential awaits when parallelism is intended:
      • Bad: const a = await p1; const b = await p2; // runs sequentially
      • Better: const [a, b] = await Promise.all([p1, p2]); // runs in parallel

    Tips & best practices

    • Prefer top-level await only in modules where available and when it simplifies bootstrapping
    • Use Promise.all for concurrent independent tasks, use Promise.allSettled when you need results of all even if some fail
    • Always handle expected rejections: network, validation, timeouts
    • Add timeouts to network calls to avoid hanging awaits (AbortController in fetch)
    • Avoid swallowing errors silently, log or report them
    • Keep try blocks small, only wrap the await that can throw, not lots of unrelated code.
    • For retries, implement exponential backoff + jitter to reduce thundering herd problems
    • Use typed errors or error codes (class AppError extends Error) so handlers can decide behavior based on error type
    • When returning errors to users, avoid leaking implementation details (stack traces). Use user-friendly messages at boundaries

    Tools and libraries that help

    • Node / Browser built-ins:
      • AbortController (fetch timeouts/cancel)
      • global Promise utilities
    • Fetch wrappers:
      • ky — small fetch wrapper with timeout, JSON handling
    • Retry helpers:
      • p-retry, promise-retry – retry with backoff
    • Error reporting:
      • Sentry, LogRocket, Datadog (for production error collection)
    • TypeScript:
      • Use Result types (fp-ts, neverthrow) or typed errors to make handling explicit.
    • Linters:
      • ESLint rules: no-floating-promises, @typescript-eslint/no-misused-promises
    • Test frameworks:
      • Jest, Vitest — test async functions with async/await and mocks
    • Utilities:
      • zod or io-ts for runtime validation of async responses

    Patterns with examples

    1. Parallel fetch with fallback and aggregated handling
    
    
    
    
    
    async function getData() {
      const sources = [fetchJson('/a'), fetchJson('/b')];
      const results = await Promise.allSettled(sources);
      const successes = results
        .filter(r => r.status === 'fulfilled')
        .map(r => r.value);
      if (successes.length === 0) throw new Error('All sources failed');
      return successes[0]; // first successful
    }
    
    1. Retry with exponential backoff (simple)
    
    
    
    
    
    async function retry(fn, retries = 3) {
      let attempt = 0;
      while (attempt < retries) {
        try {
          return await fn();
        } catch (err) {
          attempt++;
          if (attempt === retries) throw err;
          await new Promise(res => setTimeout(res, 2 ** attempt * 100 + Math.random() * 100));
        }
      }
    }
    
    1. Handling streaming/long-running tasks with AbortController
    
    
    
    
    
    async function fetchWithTimeout(url, ms = 5000) {
      const ac = new AbortController();
      const id = setTimeout(() => ac.abort(), ms);
      try {
        const res = await fetch(url, { signal: ac.signal });
        return await res.json();
      } finally {
        clearTimeout(id);
      }
    }
    

    Performance considerations

    • Awaiting sequentially is slower than running promises concurrently, use Promise.all where appropriate
    • Large numbers of concurrent promises can exhaust resources (sockets, DB connections). Use pooling or concurrency limits (p-limit)
    • Memory leaks: keep references to unresolved Promises carefully, don’t store long-lived arrays of unresolved async jobs

    Error classification and handling strategy

    • Transient (network timeouts, rate limits): retry with backoff
    • Recoverable (missing cache entry): fallback or fetch master source
    • Fatal (auth failure, validation): return user-facing error, don’t retry
    • Unknown/internal (bugs): log, alert, and return generic error to users

    Testing async code

    • Always write unit tests for both success and failure cases
    • Use mocks for network/DB: jest.mock, nock (Node), msw (browser & Node)
    • Test timeouts and retries by advancing fake timers or mocking retry helpers
    • Assert side effects: ensure cleanup code (finally blocks) ran correctly

    Security and privacy considerations

    • Don’t leak sensitive data in error messages or logs.
    • Sanitize errors before sending them to the client.
    • Mask API keys and PII in error-reporting tools.

  • Debugging Sencha Ext JS

    Debugging Sencha Ext JS

    See how to fix rendering/layout bugs, event handling issues, data/store problems, memory leaks, slow UIs or integration issues with backend APIs. Ext JS apps are component-heavy and stateful. Knowing how to inspect components, stores and layouts is essential for reliable maintenance and performance.

    High‑level process

    1. Find how to reproduce the issue
    2. Use framework and browser tools to locate the component/store/lifecycle point
    3. Inspect component config/state and store data
    4. Trace the event/data flow and network activity
    5. Use profiler/heap snapshots for performance and memory issues
    6. Verify fixes in production-like builds and multiple browsers

    Tools you’ll use

    • Browser DevTools (Chrome/Chromium, Firefox): Elements/Console/Network/Sources/Performance/Memory
    • Ext-specific: Ext Inspector (browser extension or standalone component tree, configs, layouts)
    • Sencha Cmd: build, dev server, generate source maps, and manage themes/build optimizations
    • Logging/monitoring: console, remote logging (Sentry/LogRocket), HAR exports
    • Optional: Source control, CI for automated smoke/perf tests, remote debugging tools for devices

    How Ext apps are structured

    • Components: Ext.Component subclasses (panels, grids, forms, windows). They have configs, items, listeners and methods
    • Views: component hierarchies and layouts (VBox/HBox/Border/Fit/Card)
    • Controllers / ViewControllers: event handlers and glue logic (MVC/MVVM patterns)
    • ViewModel & Data Binding: bindings, formulas and session/state management
    • Stores & Models: Ext.data.Store, Ext.data.Model, proxies (ajax/rest), readers/writers, paging
    • Layout managers: compute sizes, perform reflows. Many layout bugs start here
    • Plugins & Mixins: added behavior may inject listeners or modify lifecycle.
    • ComponentQuery & global registries: Ext.getCmp, Ext.ComponentQuery.query() for locating instances
    white spiral stair

    Step‑by‑step guide

    1. Locate the component instance
    • Use Ext Inspector to browse component tree and click components to highlight DOM and show config
    • In Console: Ext.getCmp(‘componentId’) if you know id. Use Ext.ComponentQuery.query(‘componentSelector’) (e.g., ‘grid[itemId=myGrid]’) to find components. Example:
    Code
    Ext.ComponentQuery.query('gridpanel[itemId=myGrid]')[0]
    
    var c
    
    • Use component.down(), up(), ownerCt, or getRefOwner() from controllers to navigate hierarchy.
    1. Inspect config, state, and runtime props
    • In Console, once you have a component:
      • console.dir(cmp) or inspect(cmp) (Chrome) to explore methods and properties
      • Examine cmp.config, cmp.items, cmp.getStore(), cmp.getView(), cmp.getEl(), cmp.el.dom, cmp.rendered, cmp.isVisible()
    • For grids: inspect columns (cmp.headerCt), store (cmp.getStore()), selection model, view (cmp.getView()), and bufferedRenderer plugin if used
    1. Trace event handlers and listeners
    • Check cmp.listeners and cmp.hasListener(eventName). Use cmp.events to inspect observable events
    • Use Ext.util.Observable APIs; for classes that extend Observable, check using cmp.getListeners? and cmp.getObservableListeners (API may vary by version)
    • Use DevTools “Event Listener Breakpoints” for DOM events and add conditional breakpoints in JS handlers
    1. Debug store, model, and proxy flows
    • Inspect store state:
      • store.getCount(), store.getTotalCount(), store.getData().items, store.getRange(0,100)
      • Check store.getProxy() — proxy.url, reader config, writer config, and extraParams
    • Monitor network requests in DevTools (Network tab) to see request/response payloads, status codes and headers. Export HAR for sharing
    • For paging: check start/limit params and server response shape (total property expected by reader)
    1. Use breakpoints and stack traces
    • Use source maps (Sencha Cmd generates them for dev builds) to set breakpoints in original source instead of minified files. Enable source maps in browser DevTools
    • Insert debugger; statements in suspect code paths (controllers, listeners). Use conditional breakpoints for loops or frequent events
    1. Layout and rendering debugging
    • Check layout managers: call cmp.doLayout() or cmp.updateLayout() to force recompute (use carefully in production)
    • Inspect element sizes via cmp.getWidth(), getHeight(), cmp.getEl().getBoundingClientRect()
    • Look for configs that affect size: flex, width, height, min/max sizes, margins/padding. For container layouts (hbox/vbox), ensure child items have proper flex or explicit dimensions
    • Watch for nested containers with 100% heights lacking parent height, this is a common “zero height” issue
    1. Performance profiling & memory
    • Use browser Performance tab to record interactions; look for long tasks, layout thrashing and paint counts
    • Use Memory/Heap snapshots to find detached DOM nodes or retained component instances. Search for Ext.Component instances in heap snapshots to see growth over time
    • Check for unremoved listeners (listeners referencing closures preventing GC) – Ext.util.Observable listeners should be removed in destroy handlers or use managed listeners (listeners config with scope and destroy cleanup)
    1. Ext-specific debugging tips
    • Ext.getVersion() gives Ext JS version this is helpful to know for API differences
    • Ext.Loader and dynamic class loading: watch network requests for class files in dev mode. In production, classes are bundled by Sencha Cmd, use separate dev builds to debug class loading
    • Use Ext.each, Ext.Array methods and Ext.apply/Ext.merge carefully, misuse can mutate shared configs. Prefer clone/copies when reusing config objects
    • For grids with buffered rendering or infinite scrolling, inspect bufferedRenderer plugin config (view.bufferedRenderer) and server-side total/offset behavior
    • Use ComponentQuery to find components even without ids, be careful with overly broad queries for performance reasons
    Eyeglasses reflecting computer code on a monitor, ideal for technology and programming themes.

    Common pitfalls and how to avoid them

    • Relying on global ids: prefer itemId and ComponentQuery in large apps to avoid id collisions
    • Mutating prototype objects used as configs. Always clone config objects when reusing
    • Forgetting to destroy components or remove listeners. Implement destroy handlers and use managed listeners when possible
    • Using synchronous Ajax in production or blocking UI during heavy processing. Offload heavy tasks to WebWorkers or break into async chunks
    • Not enabling source maps in dev. Makes stepping through original code much easier

    Practical examples (Console snippets)

    Find a grid and its store:

    Code
    var grid = Ext.ComponentQuery.query('gridpanel[itemId=myGrid]')[0];
    console.log(grid.getStore().getData().items);
    

    Get a component by id and inspect:

    Code
    var cmp = Ext.getCmp('myPanelId');
    console.dir(cmp);
    

    See listeners on a component:

    Code
    console.log(cmp.listeners || cmp.events);
    

    Force a layout recompute:

    Code
    cmp.updateLayout(); cmp.ownerCt && cmp.ownerCt.updateLayout();
    

    Sencha Cmd & builds

    • Use Sencha Cmd to generate dev builds with source maps: builds created via sencha app watch or sencha app build development include easier-to-debug outputs. Production builds minify and concat files—use dev builds to reproduce and debug before producing a production build. Sencha Cmd also manages theming and classpath resolution

    Logging, monitoring & sharing traces

    • Use console.debug/info/warn/error with clear tags (e.g., console.debug(‘[MyApp:Grid] rowData’, rowData))
    • Capture HAR from Network panel and share with teammates
    • Export performance traces or heap snapshots. Anonymize any sensitive payloads before sharing
    • Use remote logging (Sentry/LogRocket) for production errors, but pair with local reproductions for stateful bugs

    Versioning and API differences

    • Ext JS APIs and namespaces change across major versions. Always confirm Ext.getVersion() and consult corresponding docs for that version: https://docs.sencha.com/
    • Some debugging techniques (internal property names, loader behavior) differ between classic and modern toolkits. Check which toolkit your app uses.

    Resources and references

    • Ext Inspector project/resources (search for current extension or builds compatible with your Ext version).
    • Sencha Cmd docs: https://docs.sencha.com/cmd/
    • Browser DevTools guides: Chrome DevTools docs, Firefox DevTools docs.
  • Inspecting JavaScript Framework Internals

    Inspecting JavaScript Framework Internals

    Why inspect framework internals?

    • Debugging: locate where UI state is created or changed, find origin of events or render issues
    • Learning: understand architecture, lifecycle and data flow of unfamiliar frameworks
    • Integration: hook into lifecycle, extend components or migrate features safely
    • Security & performance: spot unsafe patterns, memory leaks or heavy render hotspots

    Which frameworks this applies to

    • Component-based: React, Vue, Angular, Svelte
    • Classic/class-system frameworks: Ext JS, Dojo, Ember
    • State-management heavy: Redux/MobX/Vuex, Flux patterns

    What you can learn by inspecting

    • Component/view tree and hierarchy
    • Instance properties: props, state, internal fields, DOM refs
    • Model/data stores: shape, current values, subscribers/listeners
    • Class names and inheritance chain (prototype / ES class)
    • Event listeners and where they attach (DOM vs. framework event bus)
    • Lifecycle hooks and order of operations
    • Templates, render output, and bindings to data
    • Network/data flows that populate models (XHR/fetch/GraphQL)

    Tools to use (built‑in + popular extensions)

    • Browser DevTools (Elements/Console/Sources/Network/Performance/Memory)
    • Framework DevTools:
      • React DevTools (components, hooks, profiler)
      • Vue Devtools (component tree, Vuex inspection)
      • Angular DevTools / Augury (component tree, change detection profiling)
      • Ember Inspector (routes, components, data, templates)
      • Svelte Devtools (component state)
      • Ext JS Inspector (for Ext JS apps, component tree, configs, layouts)
    • Generic helpers:
      • Wappalyzer / BuiltWith — detect framework used
      • Redux DevTools — inspect Redux action/state history
      • XHR/fetch breakpoints and Network HAR export
      • Source-map enabled Debugger for readable stack traces
    • Code instrumentation & runtime probes:
      • console.log/console.dir, console.table
      • debugger statements and conditional breakpoints
      • Performance.mark/measure and User Timing API
      • MutationObserver and EventListener breakpoint APIs
    • Automated tools & profilers:
      • Lighthouse, WebPageTest, and framework profilers (React Profiler)

    Sample basic workflow steps for any framework

    1. Identify the framework: use Wappalyzer or inspect global objects (window.React, window.Vue, Ext, Ember)
    2. Open the appropriate framework DevTools if available (React/Vue/Ext Inspector). If not available, use DOM + Console
    3. Locate the component/view of interest using the element picker. In framework devtools, follow the component tree; in DOM inspector, find nearest root node and inspect attached data via DOM properties or dataset attributes
    4. Inspect instance state: framework tools show props/state; otherwise check element.__reactFiber / vue / Ext.getCmp or global registries used by the framework
    5. Trace event handlers: set breakpoints on event listeners (Sources → Event Listener Breakpoints) or right‑click in Console to list listeners (getEventListeners(node))
    6. Trace data flow: monitor XHR/fetch in Network, set XHR/fetch breakpoints, or instrument store actions (Redux DevTools)
    7. Debug lifecycle: set breakpoints in constructor/connectedCallback/render/useEffect/mounted hooks via source maps in the Debugger
    8. Check memory: take heap snapshots before/after actions to find detached nodes or retained closures.
    9. Confirm fixes across browsers and with production minified bundles using source maps

    Specific toolsets

    a computer screen with a keyboard and a box with a logo
    • React
      • Use React DevTools to inspect component tree, props, state, and hooks. Use the Profiler to find expensive renders
      • In Console, React adds REACT_DEVTOOLS_GLOBAL_HOOK and elements may expose internal fibers at element._reactRootContainer or el[Object.keys(el).find(k=>k.startsWith(“__reactInternalInstance”))]. Use caution as internals change across versions
      • For class components, inspect instance methods; for hooks, use DevTools hook inspection
    • Vue
      • Vue Devtools shows component tree, reactive data, and Vuex store.
      • In Console, components attach to vue on DOM nodes. Access component instance with $vm0 after selecting in devtools or use document.querySelector(…).vue
    • Angular
      • Use Angular DevTools (and Augury historically) to inspect component trees and change detection
      • Access component instance via ng.getComponent(element) in modern Angular devtools-enabled pages
    • Ext JS
      • Use Ext JS Inspector (standalone extension) to browse component tree, configs, and layouts. Ext apps commonly expose Ext global; find components with Ext.getCmp(id) or Ext.ComponentQuery.query(selector). Inspect component.config, getStore(), and getView() for grids/lists
    • Generic/class inspection
      • Inspect prototype chain: in Console run Object.getPrototypeOf(instance) repeatedly or use console.dir(instance) to view methods
      • Identify classes by constructor.name (careful: minified builds rename names). For minified code, rely on source maps
    • Events & DOM listeners
      • getEventListeners(node) in Console (Chrome) or inspect via Event Listener Breakpoints in Sources. Use MonitorEvents(node) to log events
      • For framework-emitted events, inspect framework-specific event buses (e.g., Ember.Evented, Ext.util.Observable)
    • Stores & models
      • For Redux: use Redux DevTools or window.REDUX_DEVTOOLS_EXTENSION to replay actions and inspect state diffs
      • For MobX/Vuex/Ext data stores: inspect store objects, subscribe callbacks, and check mutation logs where available

    Tips

    • Enable source maps in production builds for easier debugging, if not available, use pretty-print in Sources
    • Use conditional breakpoints to avoid stepping through tight loops
    • Avoid relying on private internals in production code, use public APIs or official hooks.
    • When inspecting minified/prod builds, map stack traces with source maps or re-run a non-minified local build for debugging
    • Use read-only inspection first, prefer not to change app state in production unless safe and reversible

    Privacy/security note

    • Don’t paste secrets or PII into console or external debugging tools. Use sanitized HARs and recordings when sharing