or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

async-iteration.mddebug-system.mdevent-emission.mdevent-subscription.mdindex.mdutilities.md
tile.json

event-emission.mddocs/

Event Emission

Asynchronous event emission with both concurrent and sequential execution patterns for triggering listeners.

Capabilities

Concurrent Event Emission

Trigger events asynchronously with all listeners executing concurrently for maximum performance.

/**
 * Trigger an event asynchronously with concurrent listener execution
 * @param eventName - The name of the event to emit
 * @param eventData - Optional data to pass to event listeners
 * @returns Promise that resolves when all listeners complete or rejects if any listener throws
 */
emit<Name extends keyof EventData>(
  eventName: Name,
  eventData: EventData[Name]
): Promise<void>;

/**
 * Trigger an event asynchronously without data
 * @param eventName - The name of the event to emit
 * @returns Promise that resolves when all listeners complete
 */
emit(eventName: EventName): Promise<void>;

Usage Examples:

import Emittery from "emittery";

const emitter = new Emittery();

// Emit with data
await emitter.emit('user-created', {
  id: '123',
  name: 'Alice',
  email: 'alice@example.com'
});

// Emit without data
await emitter.emit('ready');

// All listeners run concurrently
emitter.on('process-data', async (data) => {
  // This listener might complete first
  await fastOperation(data);
});

emitter.on('process-data', async (data) => {
  // This listener runs in parallel
  await slowOperation(data);
});

// Both listeners start immediately when emitted
await emitter.emit('process-data', someData);
console.log('All listeners completed');

Sequential Event Emission

Trigger events asynchronously with listeners executing one after another in sequence.

/**
 * Trigger an event asynchronously with sequential listener execution
 * @param eventName - The name of the event to emit
 * @param eventData - Optional data to pass to event listeners
 * @returns Promise that resolves when all listeners complete sequentially, or rejects on first failure
 */
emitSerial<Name extends keyof EventData>(
  eventName: Name,
  eventData: EventData[Name]
): Promise<void>;

/**
 * Trigger an event asynchronously without data, with sequential listener execution
 * @param eventName - The name of the event to emit
 * @returns Promise that resolves when all listeners complete
 */
emitSerial(eventName: EventName): Promise<void>;

Usage Examples:

import Emittery from "emittery";

const emitter = new Emittery();

// Sequential processing where order matters
emitter.on('pipeline', async (data) => {
  console.log('Step 1: Validating');
  await validateData(data);
});

emitter.on('pipeline', async (data) => {
  console.log('Step 2: Processing');
  await processData(data);
});

emitter.on('pipeline', async (data) => {
  console.log('Step 3: Saving');
  await saveData(data);
});

// Listeners execute in the order they were registered
await emitter.emitSerial('pipeline', inputData);
console.log('Pipeline completed');

// If any step fails, remaining steps won't execute
try {
  await emitter.emitSerial('critical-process', data);
} catch (error) {
  console.error('Process failed at step:', error);
}

Error Handling

Both emission methods handle errors differently:

Concurrent Emission (emit):

  • If any listener throws an error, the returned promise rejects with that error
  • Other listeners continue executing and are not affected
  • Use this when listeners are independent

Sequential Emission (emitSerial):

  • If any listener throws an error, execution stops immediately
  • Remaining listeners in the sequence are not called
  • Use this when listeners depend on each other
import Emittery from "emittery";

const emitter = new Emittery();

// Concurrent error handling
emitter.on('data', async () => {
  throw new Error('Listener 1 failed');
});

emitter.on('data', async () => {
  console.log('Listener 2 still runs');
});

try {
  await emitter.emit('data');
} catch (error) {
  console.error('One listener failed:', error.message);
  // Listener 2 still executed
}

// Sequential error handling
emitter.on('sequence', async () => {
  throw new Error('Step 1 failed');
});

emitter.on('sequence', async () => {
  console.log('This will not run');
});

try {
  await emitter.emitSerial('sequence');
} catch (error) {
  console.error('Sequence stopped:', error.message);
  // Second listener never executed
}

Event Name Types

Emittery accepts various event name types for flexibility:

type EventName = PropertyKey; // string | symbol | number

Usage Examples:

import Emittery from "emittery";

const emitter = new Emittery();

// String event names (most common)
await emitter.emit('user-login', userData);

// Symbol event names (collision-resistant)
const INTERNAL_EVENT = Symbol('internal');
await emitter.emit(INTERNAL_EVENT, internalData);

// Number event names
await emitter.emit(404, errorData);

Performance Considerations

  • Use emit() for independent listeners: When listeners don't depend on each other's completion
  • Use emitSerial() for dependent operations: When listeners must complete in order
  • Async listeners are preferred: Synchronous listeners still work but may block the event loop
  • Error boundaries: Wrap emission in try-catch blocks to handle listener errors gracefully
import Emittery from "emittery";

const emitter = new Emittery();

// Good: Independent operations use concurrent emission
await emitter.emit('log-activity', activityData);
await emitter.emit('update-metrics', metricsData);

// Good: Dependent operations use sequential emission
await emitter.emitSerial('validation-chain', inputData);

// Good: Error handling
try {
  await emitter.emit('risky-operation', data);
} catch (error) {
  console.error('Operation failed:', error);
  await emitter.emit('operation-failed', {error, data});
}