A JavaScript framework for creating ambitious web applications
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Resource management system for automatic cleanup of objects and prevention of memory leaks.
Core functions for managing object destruction and cleanup lifecycle.
/**
* Destroy an object and run all registered destructors
* @param destroyable - Object to destroy
*/
function destroy(destroyable: object): void;
/**
* Check if an object is currently being destroyed
* @param destroyable - Object to check
* @returns Whether object is in destruction process
*/
function isDestroying(destroyable: object): boolean;
/**
* Check if an object has been completely destroyed
* @param destroyable - Object to check
* @returns Whether object is destroyed
*/
function isDestroyed(destroyable: object): boolean;Usage Examples:
import { destroy, isDestroying, isDestroyed } from "@ember/destroyable";
import Component from "@glimmer/component";
export default class WebSocketComponent extends Component {
socket = null;
constructor(owner, args) {
super(owner, args);
this.setupWebSocket();
}
setupWebSocket() {
this.socket = new WebSocket('ws://localhost:8080');
this.socket.onmessage = (event) => {
// Only process messages if component not destroyed
if (!isDestroyed(this)) {
this.handleMessage(JSON.parse(event.data));
}
};
}
handleMessage(data) {
// Check if destruction is in progress
if (isDestroying(this)) {
console.log('Component destroying, ignoring message');
return;
}
// Process message
this.args.onMessage?.(data);
}
willDestroy() {
super.willDestroy();
// Manual cleanup
if (this.socket) {
this.socket.close();
this.socket = null;
}
}
}
// Programmatic destruction
const component = SomeComponent.create();
// ... use component
destroy(component); // Triggers cleanup
console.log(isDestroyed(component)); // trueSystem for registering cleanup functions that run when objects are destroyed.
/**
* Register a destructor function to run when object is destroyed
* @param destroyable - Object to register destructor for
* @param destructor - Function to run on destruction
* @param eager - Whether to run destructor eagerly (before other cleanup)
*/
function registerDestructor(destroyable: object, destructor: (destroyable: object) => void, eager?: boolean): void;
/**
* Unregister a previously registered destructor
* @param destroyable - Object the destructor was registered for
* @param destructor - Destructor function to unregister
*/
function unregisterDestructor(destroyable: object, destructor: (destroyable: object) => void): void;Usage Examples:
import { registerDestructor, unregisterDestructor } from "@ember/destroyable";
import Service from "@ember/service";
export default class DatabaseService extends Service {
connections = new Map();
init() {
super.init();
// Register cleanup for all connections
registerDestructor(this, (service) => {
console.log('Cleaning up database connections...');
service.connections.forEach(connection => {
connection.close();
});
service.connections.clear();
});
}
createConnection(name, config) {
const connection = new DatabaseConnection(config);
this.connections.set(name, connection);
// Register destructor for individual connection
const connectionDestructor = () => {
console.log(`Closing connection: ${name}`);
connection.close();
this.connections.delete(name);
};
registerDestructor(this, connectionDestructor);
// Return function to manually close connection
return () => {
unregisterDestructor(this, connectionDestructor);
connectionDestructor();
};
}
}
// Component with resource cleanup
export default class TimerComponent extends Component {
timerId = null;
constructor(owner, args) {
super(owner, args);
this.startTimer();
// Register cleanup for timer
registerDestructor(this, (component) => {
if (component.timerId) {
clearInterval(component.timerId);
component.timerId = null;
}
});
}
startTimer() {
this.timerId = setInterval(() => {
if (!isDestroyed(this)) {
this.args.onTick?.();
}
}, 1000);
}
}System for associating child objects with parents for automatic cleanup.
/**
* Associate a child destroyable with a parent for automatic cleanup
* When parent is destroyed, child will be destroyed too
* @param parent - Parent object
* @param child - Child object to associate
*/
function associateDestroyableChild(parent: object, child: object): void;Usage Examples:
import { associateDestroyableChild } from "@ember/destroyable";
import EmberObject from "@ember/object";
export default class ParentService extends Service {
children = new Set();
createChild(config) {
const child = ChildObject.create(config);
this.children.add(child);
// Associate child with parent for automatic cleanup
associateDestroyableChild(this, child);
return child;
}
createWorker(taskConfig) {
const worker = new Worker('worker.js');
const workerWrapper = EmberObject.create({
worker,
postMessage(data) {
this.worker.postMessage(data);
},
terminate() {
this.worker.terminate();
}
});
// Auto-cleanup worker when service is destroyed
associateDestroyableChild(this, workerWrapper);
// Register destructor for worker cleanup
registerDestructor(workerWrapper, (wrapper) => {
wrapper.worker.terminate();
});
return workerWrapper;
}
}
// Factory pattern with automatic cleanup
class ResourceFactory {
static createResource(parent, type, config) {
let resource;
switch (type) {
case 'database':
resource = DatabaseResource.create(config);
break;
case 'cache':
resource = CacheResource.create(config);
break;
case 'queue':
resource = QueueResource.create(config);
break;
default:
throw new Error(`Unknown resource type: ${type}`);
}
// Associate with parent for cleanup
associateDestroyableChild(parent, resource);
return resource;
}
}
export default class ApplicationService extends Service {
init() {
super.init();
// Create resources that will be auto-cleaned up
this.database = ResourceFactory.createResource(this, 'database', {
host: 'localhost',
port: 5432
});
this.cache = ResourceFactory.createResource(this, 'cache', {
maxSize: 1000
});
this.queue = ResourceFactory.createResource(this, 'queue', {
maxJobs: 10
});
}
}Advanced patterns for complex cleanup scenarios.
/**
* Enable eager destruction (immediate cleanup) for an object
* @param destroyable - Object to enable eager destruction for
*/
function enableDestroyableTracking(destroyable: object): void;
/**
* Check if destruction tracking is enabled for an object
* @param destroyable - Object to check
* @returns Whether tracking is enabled
*/
function isDestroyableTrackingEnabled(destroyable: object): boolean;Usage Examples:
import {
registerDestructor,
associateDestroyableChild,
enableDestroyableTracking
} from "@ember/destroyable";
// Resource pool with automatic cleanup
export default class ResourcePool extends Service {
pool = [];
activeResources = new Set();
init() {
super.init();
// Enable tracking for immediate cleanup feedback
enableDestroyableTracking(this);
// Register pool cleanup
registerDestructor(this, (pool) => {
console.log(`Cleaning up resource pool with ${pool.activeResources.size} active resources`);
// Force cleanup of all active resources
pool.activeResources.forEach(resource => {
resource.forceCleanup();
});
pool.pool.length = 0;
pool.activeResources.clear();
}, true); // Eager cleanup
}
acquireResource() {
let resource = this.pool.pop();
if (!resource) {
resource = this.createResource();
}
this.activeResources.add(resource);
// Auto-cleanup when parent is destroyed
associateDestroyableChild(this, resource);
return resource;
}
releaseResource(resource) {
this.activeResources.delete(resource);
if (resource.isReusable() && !isDestroyed(this)) {
resource.reset();
this.pool.push(resource);
} else {
destroy(resource);
}
}
createResource() {
const resource = ResourceObject.create({
pool: this,
forceCleanup() {
this.cleanup();
this.pool = null;
}
});
// Register individual resource cleanup
registerDestructor(resource, (res) => {
if (res.pool) {
res.pool.activeResources.delete(res);
}
res.cleanup();
});
return resource;
}
}Utilities and patterns for preventing common memory leaks.
/**
* Create a weak reference that doesn't prevent garbage collection
* @param target - Object to create weak reference to
* @returns Weak reference wrapper
*/
function createWeakRef(target: object): WeakRef<object>;
/**
* Register callback to run when object is garbage collected
* @param target - Object to watch
* @param callback - Function to run on GC
* @returns FinalizationRegistry entry
*/
function onGarbageCollect(target: object, callback: () => void): any;Usage Examples:
// Preventing circular references with weak references
export default class EventBus extends Service {
listeners = new Map();
weakRefs = new Map();
subscribe(target, eventName, callback) {
// Create weak reference to prevent memory leaks
const weakRef = new WeakRef(target);
const listenerId = Math.random().toString(36);
const listener = {
id: listenerId,
eventName,
callback,
target: weakRef
};
if (!this.listeners.has(eventName)) {
this.listeners.set(eventName, new Set());
}
this.listeners.get(eventName).add(listener);
this.weakRefs.set(listenerId, listener);
// Cleanup when target is destroyed
registerDestructor(target, () => {
this.unsubscribe(listenerId);
});
return listenerId;
}
emit(eventName, data) {
const listeners = this.listeners.get(eventName);
if (!listeners) return;
// Clean up dead weak references
const deadListeners = [];
listeners.forEach(listener => {
const target = listener.target.deref();
if (target && !isDestroyed(target)) {
listener.callback.call(target, data);
} else {
deadListeners.push(listener);
}
});
// Remove dead listeners
deadListeners.forEach(listener => {
listeners.delete(listener);
this.weakRefs.delete(listener.id);
});
}
unsubscribe(listenerId) {
const listener = this.weakRefs.get(listenerId);
if (listener) {
const listeners = this.listeners.get(listener.eventName);
if (listeners) {
listeners.delete(listener);
}
this.weakRefs.delete(listenerId);
}
}
}interface Destroyable {
/** Whether object is being destroyed */
isDestroying?: boolean;
/** Whether object is destroyed */
isDestroyed?: boolean;
/** Cleanup method (optional) */
destroy?(): void;
/** Pre-destruction hook (optional) */
willDestroy?(): void;
}
interface DestructorFunction {
/** Function called when object is destroyed */
(destroyable: object): void;
}
interface DestroyableChild extends Destroyable {
/** Parent destroyable object */
parent?: Destroyable;
}
interface ResourceWrapper {
/** Wrapped resource */
resource: any;
/** Cleanup function */
cleanup(): void;
/** Whether resource is reusable */
isReusable(): boolean;
/** Reset resource for reuse */
reset(): void;
}Install with Tessl CLI
npx tessl i tessl/npm-ember-source