Implementing serviceToggler in JavaScript: A Step-by-Step Guide
Overview
serviceToggler is a lightweight pattern for enabling/disabling application services (features, modules, background tasks) at runtime. This guide shows a simple, testable JavaScript implementation suitable for frontend or Node.js apps, with examples for synchronous and asynchronous services, persistence, and events.
1. Goals and API
- Enable/disable named services at runtime.
- Query current state and subscribe to changes.
- Support synchronous and async start/stop hooks.
- Optional persistence (localStorage or a backend).
- Simple API:
- register(name, { start, stop, autostart })
- enable(name)
- disable(name)
- toggle(name)
- isEnabled(name)
- onChange(callback)
2. Core Implementation (plain JavaScript)
javascript
// serviceToggler.js export function createServiceToggler({ storage } = {}) { const services = new Map(); const listeners = new Set(); function notify(name, enabled) { for (const cb of listeners) cb(name, enabled); } async function register(name, { start, stop, autostart = false } = {}) { if (services.has(name)) throw new Error(</span><span class="token template-string" style="color: rgb(163, 21, 21);">Service "</span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">${</span><span class="token template-string interpolation">name</span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">}</span><span class="token template-string" style="color: rgb(163, 21, 21);">" exists</span><span class="token template-string template-punctuation" style="color: rgb(163, 21, 21);">); services.set(name, { start: start || (async () => {}), stop: stop || (async () => {}), enabled: false, }); const saved = storage?.getItem?.(</span><span class="token template-string" style="color: rgb(163, 21, 21);">service:</span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">${</span><span class="token template-string interpolation">name</span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">}</span><span class="token template-string template-punctuation" style="color: rgb(163, 21, 21);">); if (saved === ‘true’ || autostart) { await enable(name); } } async function enable(name) { const svc = services.get(name); if (!svc) throw new Error(</span><span class="token template-string" style="color: rgb(163, 21, 21);">Unknown service "</span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">${</span><span class="token template-string interpolation">name</span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">}</span><span class="token template-string" style="color: rgb(163, 21, 21);">"</span><span class="token template-string template-punctuation" style="color: rgb(163, 21, 21);">); if (svc.enabled) return; await svc.start(); svc.enabled = true; storage?.setItem?.(</span><span class="token template-string" style="color: rgb(163, 21, 21);">service:</span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">${</span><span class="token template-string interpolation">name</span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">}</span><span class="token template-string template-punctuation" style="color: rgb(163, 21, 21);">, ‘true’); notify(name, true); } async function disable(name) { const svc = services.get(name); if (!svc) throw new Error(</span><span class="token template-string" style="color: rgb(163, 21, 21);">Unknown service "</span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">${</span><span class="token template-string interpolation">name</span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">}</span><span class="token template-string" style="color: rgb(163, 21, 21);">"</span><span class="token template-string template-punctuation" style="color: rgb(163, 21, 21);">); if (!svc.enabled) return; await svc.stop(); svc.enabled = false; storage?.setItem?.(</span><span class="token template-string" style="color: rgb(163, 21, 21);">service:</span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">${</span><span class="token template-string interpolation">name</span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">}</span><span class="token template-string template-punctuation" style="color: rgb(163, 21, 21);">, ‘false’); notify(name, false); } async function toggle(name) { const svc = services.get(name); if (!svc) throw new Error(</span><span class="token template-string" style="color: rgb(163, 21, 21);">Unknown service "</span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">${</span><span class="token template-string interpolation">name</span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">}</span><span class="token template-string" style="color: rgb(163, 21, 21);">"</span><span class="token template-string template-punctuation" style="color: rgb(163, 21, 21);">); return svc.enabled ? disable(name) : enable(name); } function isEnabled(name) { const svc = services.get(name); return !!svc && svc.enabled; } function onChange(cb) { listeners.add(cb); return () => listeners.delete(cb); } return { register, enable, disable, toggle, isEnabled, onChange }; }
3. Example Usage
javascript
import { createServiceToggler } from ’./serviceToggler.js’; const toggler = createServiceToggler({ storage: localStorage }); await toggler.register(‘analytics’, { start: async () => { await loadAnalyticsScript(); }, stop: async () => { window.analytics?.shutdown?.(); }, autostart: false, }); await toggler.register(‘syncWorker’, { start: () => navigator.serviceWorker.register(’/sw.js’), stop: () => caches.delete(‘sync-cache’), autostart: true, }); toggler.onChange((name, enabled) => { console.log(</span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">${</span><span class="token template-string interpolation">name</span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">}</span><span class="token template-string" style="color: rgb(163, 21, 21);"> is now </span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">${</span><span class="token template-string interpolation">enabled </span><span class="token template-string interpolation" style="color: rgb(57, 58, 52);">?</span><span class="token template-string interpolation"> </span><span class="token template-string interpolation" style="color: rgb(163, 21, 21);">'enabled'</span><span class="token template-string interpolation"> </span><span class="token template-string interpolation" style="color: rgb(57, 58, 52);">:</span><span class="token template-string interpolation"> </span><span class="token template-string interpolation" style="color: rgb(163, 21, 21);">'disabled'</span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">}</span><span class="token template-string template-punctuation" style="color: rgb(163, 21, 21);">); }); await toggler.toggle(‘analytics’);
4. Handling Errors & Timeouts
- Wrap start/stop with timeouts to avoid hangs.
- Use try/catch and optionally revert state if start fails.
javascript
async function withTimeout(promise, ms = 5000) { let id; const timeout = new Promise((_, rej) => id = setTimeout(() => rej(new Error(‘Timeout’)), ms)); try { return await Promise.race([promise, timeout]); } finally { clearTimeout(id); } }
5. Advanced Features
- Dependency management: prevent enabling unless dependencies are enabled.
- Group toggles and bulk operations.
- Server-side sync: fetch user preferences and restore states on init.
- Feature flags integration: wrap serviceToggler with remote feature flag evaluations.
6. Testing
- Unit test register/enable/disable/toggle behaviors with mocked start/stop.
- Test persistence by injecting in-memory storage.
- Test error and timeout paths.
7. Security & Performance Notes
- Keep start/stop idempotent.
- Avoid heavy work on the main thread; use web workers or background tasks.
- Validate input names to prevent injection issues when used in storage keys.
8. Summary
Use this pattern to centrally manage runtime services with clear lifecycle hooks, persistence, and eventing. The provided implementation is minimal and extensible for dependency handling, timeouts, and remote syncing.
Leave a Reply