| name | javascript-async-patterns |
| description | Comprehensive guide for JavaScript asynchronous programming patterns. This skill should be used when implementing async/await control flow, EventEmitter-based architectures, connection state machines, retry logic, or handling complex asynchronous operations in Node.js applications. |
JavaScript Async Patterns
This skill provides patterns and best practices for asynchronous JavaScript programming, covering async/await control flow, event-driven architecture, and connection state management.
When to Use This Skill
- Implementing async/await with proper error handling
- Building event-driven systems with EventEmitter
- Managing connection lifecycles (connect, reconnect, disconnect)
- Implementing state machines for connection management
- Adding retry logic with exponential backoff
- Handling timeouts and cancellation
- Managing concurrent operations
Core Patterns Overview
Async/Await Fundamentals
async function operation() {
try {
const result = await someAsyncWork();
return result;
} catch (err) {
throw err;
}
}
Sequential vs Parallel Execution
async function sequential(items) {
const results = [];
for (const item of items) {
results.push(await processItem(item));
}
return results;
}
async function parallel(items) {
return Promise.all(items.map(item => processItem(item)));
}
async function parallelLimited(items, limit = 5) {
const results = [];
const executing = new Set();
for (const item of items) {
const promise = processItem(item).then(result => {
executing.delete(promise);
return result;
});
executing.add(promise);
results.push(promise);
if (executing.size >= limit) {
await Promise.race(executing);
}
}
return Promise.all(results);
}
Timeout Pattern
function withTimeout(promise, ms, message = 'Operation timed out') {
let timeoutId;
const timeout = new Promise((_, reject) => {
timeoutId = setTimeout(() => reject(new Error(message)), ms);
});
return Promise.race([promise, timeout]).finally(() => {
clearTimeout(timeoutId);
});
}
const result = await withTimeout(fetchData(), 5000);
Retry with Exponential Backoff
async function withRetry(fn, options = {}) {
const {
maxAttempts = 3,
baseDelay = 1000,
maxDelay = 30000,
factor = 2,
shouldRetry = () => true
} = options;
let lastError;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn(attempt);
} catch (err) {
lastError = err;
if (attempt === maxAttempts || !shouldRetry(err, attempt)) {
throw err;
}
const delay = Math.min(
baseDelay * Math.pow(factor, attempt - 1),
maxDelay
);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
Cancellation with AbortController
async function cancellableOperation(signal) {
if (signal?.aborted) {
throw new Error('Operation cancelled');
}
return new Promise((resolve, reject) => {
const onAbort = () => {
reject(new Error('Operation cancelled'));
};
signal?.addEventListener('abort', onAbort);
doAsyncWork()
.then(resolve)
.catch(reject)
.finally(() => {
signal?.removeEventListener('abort', onAbort);
});
});
}
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
try {
await cancellableOperation(controller.signal);
} catch (err) {
if (err. === ) {
}
}
EventEmitter Patterns
Basic EventEmitter Usage
const EventEmitter = require('events');
class MyService extends EventEmitter {
constructor() {
super();
this.state = 'idle';
}
async connect() {
this.emit('connecting');
try {
await this._doConnect();
this.state = 'connected';
this.emit('connected');
} catch (err) {
this.emit('error', err);
throw err;
}
}
async _doConnect() {
}
}
Listener Lifecycle Management
class ConnectionManager extends EventEmitter {
constructor() {
super();
this._boundHandlers = new Map();
}
_bindHandler(emitter, event, handler) {
const bound = handler.bind(this);
this._boundHandlers.set(handler, bound);
emitter.on(event, bound);
return bound;
}
_unbindHandler(emitter, event, handler) {
const bound = this._boundHandlers.get(handler);
if (bound) {
emitter.off(event, bound);
this._boundHandlers.delete(handler);
}
}
destroy() {
this.removeAllListeners();
this._boundHandlers.clear();
}
}
Once Pattern for Single Events
const { once } = require('events');
async function waitForConnection(emitter) {
const [connection] = await once(emitter, 'connected');
return connection;
}
async function waitForConnectionWithTimeout(emitter, timeout) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const [connection] = await once(emitter, 'connected', {
signal: controller.signal
});
return connection;
} finally {
clearTimeout(timeoutId);
}
}
Error Event Handling
class RobustEmitter extends EventEmitter {
constructor() {
super();
this.on('error', (err) => {
if (this.listenerCount('error') === 1) {
console.error('Unhandled emitter error:', err);
}
});
}
safeEmit(event, ...args) {
try {
return this.emit(event, ...args);
} catch (err) {
this.emit('error', err);
return false;
}
}
}
Connection State Management
State Machine Pattern
const STATES = {
DISCONNECTED: 'disconnected',
CONNECTING: 'connecting',
CONNECTED: 'connected',
RECONNECTING: 'reconnecting',
DISCONNECTING: 'disconnecting',
FAILED: 'failed'
};
const TRANSITIONS = {
[STATES.DISCONNECTED]: ['connecting'],
[STATES.CONNECTING]: ['connected', 'disconnected', 'failed'],
[STATES.CONNECTED]: ['disconnecting', 'reconnecting'],
[STATES.RECONNECTING]: ['connected', 'disconnected', 'failed'],
[STATES.DISCONNECTING]: ['disconnected'],
[STATES.FAILED]: ['connecting', 'disconnected']
};
class ConnectionStateMachine extends EventEmitter {
constructor() {
super();
this._state = STATES.;
}
() {
.;
}
() {
allowed = [.];
allowed && allowed.(newState);
}
() {
(!.(newState)) {
(
);
}
oldState = .;
. = newState;
.(, { : oldState, : newState });
.(newState);
;
}
() {
. === .;
}
() {
[., .].(.);
}
() {
.(.);
}
}
Full Connection Manager Implementation
class ConnectionManager extends ConnectionStateMachine {
constructor(options = {}) {
super();
this.options = {
reconnect: true,
maxReconnectAttempts: 5,
reconnectDelay: 1000,
maxReconnectDelay: 30000,
reconnectFactor: 2,
connectionTimeout: 10000,
...options
};
this._reconnectAttempts = 0;
this._connection = null;
this._reconnectTimeout = null;
}
async connect() {
if (!this.canConnect()) {
if (this.isConnected()) return;
throw new Error(`Cannot connect from state: ${this.state}`);
}
this._transition(STATES.);
. = ;
{
.();
} (err) {
.(err);
err;
}
}
() {
connection = (
.(),
..,
);
. = connection;
.();
.(.);
}
() {
();
}
() {
(!.) ;
..(, {
.(, err);
});
..(, {
.();
});
}
() {
. = ;
(. === .) {
.(.);
;
}
(.. && .()) {
.();
} {
.(.);
}
}
() {
. < ..;
}
() {
.(.);
.++;
delay = .(
.. *
.(.., . - ),
..
);
.(, {
: .,
delay,
: ..
});
. = ( () => {
{
.();
. = ;
} (err) {
(.()) {
.();
} {
.(err);
}
}
}, delay);
}
() {
.(.);
.(, err);
}
() {
(.) {
(.);
. = ;
}
(. === .) ;
.(.);
(.) {
{
.();
} (err) {
.(, err);
}
. = ;
}
.(.);
}
() {
}
() {
.();
.();
}
}
Error Handling Best Practices
Async Error Propagation
function badExample() {
setTimeout(async () => {
await riskyOperation();
}, 1000);
}
function goodExample() {
setTimeout(async () => {
try {
await riskyOperation();
} catch (err) {
handleError(err);
}
}, 1000);
}
class Service extends EventEmitter {
scheduleTask() {
setTimeout(async () => {
try {
await this.riskyOperation();
} catch (err) {
this.emit('error', err);
}
}, 1000);
}
}
Cleanup on Error
async function operationWithCleanup() {
let resource = null;
try {
resource = await acquireResource();
const result = await useResource(resource);
return result;
} finally {
if (resource) {
try {
await releaseResource(resource);
} catch (cleanupErr) {
console.error('Cleanup error:', cleanupErr);
}
}
}
}
Graceful Shutdown Pattern
class Application {
constructor() {
this._shutdownPromise = null;
this._isShuttingDown = false;
}
async shutdown(signal) {
if (this._shutdownPromise) {
return this._shutdownPromise;
}
this._isShuttingDown = true;
this._shutdownPromise = this._performShutdown(signal);
return this._shutdownPromise;
}
async _performShutdown(signal) {
console.log(`Shutting down (${signal})...`);
await this.stopAcceptingWork();
await this.waitForInFlight(30000);
await this.();
.();
}
() {
= () => {
.(signal).( {
process.();
}).( {
.(, err);
process.();
});
};
process.(, ());
process.(, ());
}
}
Reference Documentation
For detailed patterns and examples, consult the references directory:
references/async-await-patterns.md - Advanced async/await patterns
references/eventemitter-patterns.md - EventEmitter best practices
references/state-machine-patterns.md - Connection state machines
To search for specific patterns:
grep -r "pattern" references/