isotropic-initializable
v0.12.0
Published
An observable initialization lifecycle
Downloads
28
Readme
isotropic-initializable
An observable initialization lifecycle for JavaScript objects. Ensures parent-to-child initialization sequence, supports asynchronous initialization, and provides event-based completion notifications.
Why Use This?
- Predictable Initialization Order: Ensures parent class initialization completes before child classes initialize
- Asynchronous Support: Works with both synchronous and promise-based initialization methods
- Observable Lifecycle Events: Subscribe to initialization events for greater control
- Mixin Support: Works seamlessly with
isotropic-makemixins - Selective Initialization: Configure which classes in the hierarchy should initialize
- Error Handling: Built-in error propagation for initialization failures
The Initialization Problem
When using hierarchical inheritance in JavaScript, the standard pattern with isotropic-make (or ES6 classes) runs constructors from parent to child, but then instance methods run in reverse order (child to parent). For complex objects that need a reliable initialization sequence, this can be problematic.
The isotropic-initializable module solves this by providing a dedicated initialization phase that runs in a predictable parent-to-child order, ensuring each component has a stable foundation to build upon.
Installation
npm install isotropic-initializableBasic Usage
import _Initializable from 'isotropic-initializable';
import _make from 'isotropic-make';
// Create a base component with initialization
const _BaseComponent = _make(_Initializable, {
_initialize () {
console.log('Base component initializing...');
this.baseReady = true;
// Can return a Promise if needed
}
}),
// Create a derived component
_EnhancedComponent = _make(_BaseComponent, {
_initialize () {
console.log('Enhanced component initializing...');
// Safe to use this.baseReady here, parent initialization completes first
this.enhancedReady = true;
}
});
{
// Instance is automatically initialized during construction
const component = _EnhancedComponent();
// Outputs:
// Base component initializing...
// Enhanced component initializing...
console.log(component.initialized); // true
}Key Concepts
Initialization Order
- The
isotropic-initializablemodule ensures initialization methods are called in parent-to-child order - For each class in the inheritance chain, its
_initializemethod is called - Mixins are initialized in the order they were defined
- Any asynchronous initialization is properly awaited before proceeding to child classes
Observable Lifecycle
The initialization process includes several observable events:
- initialize: Triggered when initialization begins
- initializeComplete: Triggered when all initialization has completed successfully
- initializeError: Triggered if an error occurs during initialization
const component = _Component({
initialize: false
});
// Listen for complete initialization
component.on('initializeComplete', () => {
console.log('Component is fully initialized!');
});
// Listen for initialization errors
component.on('initializeError', ({
data
}) => {
console.error('Initialization failed:', data.error);
});
// Start initialization
component.initialize();Initialization Status
You can check the initialization status of any Initializable object:
const component = _Component();
console.log(component.initialized); // true if initialization completedExamples
Basic Synchronous Initialization
import _Initializable from 'isotropic-initializable';
import _make from 'isotropic-make';
const _Widget = _make(_Initializable, {
render () {
document.body.appendChild(this.elements.container);
},
_initialize (config) {
console.log('Initializing widget with:', config);
this.elements = {};
this.name = config.name;
// Create DOM elements
this.elements.container = document.createElement('div');
this.elements.container.className = 'widget';
}
});
{
// Create and initialize
const widget = _Widget({
name: 'MyWidget'
});
// Already initialized
widget.render();
}Asynchronous Initialization
import _Initializable from 'isotropic-initializable';
import _make from 'isotropic-make';
const _DataComponent = _make(_Initializable, {
displayData () {
console.log('Displaying:', this.data);
},
async _initialize (config) {
console.log('Loading data...');
// API call
this.data = await fetch(`https://api.example.com/data/${config.id}`).then(response => response.json());
console.log('Data loaded!');
}
});
{
// Create and automatically initialize
const component = _DataComponent({
id: '123'
});
// Listen for completion
component.on('initializeComplete', () => {
component.displayData();
});
}Delayed Initialization
import _Initializable from 'isotropic-initializable';
import _make from 'isotropic-make';
const _LazyComponent = _make(_Initializable, {
_initialize() {
console.log('Initializing expensive resources...');
// Do expensive initialization
}
});
{
// Create without initializing
const component = _LazyComponent({
initialize: false
});
console.log(component.initialized); // false
// Listen for completion
component.on('initializeComplete', () => {
console.log('Now ready to use!');
});
// Later, when needed
button.addEventListener('click', () => {
component.initialize();
});
}Multi-level Inheritance
import _Initializable from 'isotropic-initializable';
import _make from 'isotropic-make';
// Base component
const _UiComponent = _make(_Initializable, {
_initialize (config) {
console.log('UiComponent initializing');
this.id = config.id || `ui-${Date.now()}`;
this.element = document.createElement('div');
this.element.id = this.id;
}
}),
// Mid-level component
_Container = _make(_UiComponent, {
_initialize (config) {
console.log('Container initializing');
this.children = [];
this.element.className = 'container';
if (config.styles) {
Object.assign(this.element.style, config.styles);
}
}
}),
// Leaf component
_Panel = _make(_Container, {
addContent (content) {
this.content.appendChild(content);
return this;
},
_initialize (config) {
console.log('Panel initializing');
// Create header
this.header = document.createElement('header');
this.header.textContent = config.title || 'Untitled Panel';
this.element.appendChild(this.header);
// Create content area
this.content = document.createElement('div');
this.content.className = 'panel-content';
this.element.appendChild(this.content);
}
});
{
// Create instance - initialization runs in order:
// 1. _UiComponent._initialize
// 2. _Container._initialize
// 3. _Panel._initialize
const panel = _Panel({
id: 'main-panel',
styles: {
height: '300px',
width: '500px'
}
title: 'System Status'
});
document.body.appendChild(panel.element);
}Using with Mixins
import _Initializable from 'isotropic-initializable';
import _make from 'isotropic-make';
// Create mixins
const _Resizable = _make({
resize (width, height) {
this.width = width ?? this.width;
this.height = height ?? this.height;
this.updateSize();
},
updateSize () {
console.log(`Setting size: ${this.width}x${this.height}`);
// Update size
},
_initialize (config) {
console.log('Initializing resize support');
this.height = config.height ?? 200;
this.width = config.width ?? 300;
this.updateSize();
}
}),
_Themeable = _make({
applyTheme (theme) {
console.log(`Applying theme: ${theme}`);
// Apply theme styles
},
toggleTheme () {
this.theme = this.theme === 'light' ?
'dark' :
'light';
this.applyTheme(this.theme);
}
_initialize (config) {
console.log('Initializing theme support');
this.theme = config.theme ?? 'light';
this.applyTheme(this.theme);
}
}),
// Create a component with mixins
_MyComponent = _make(_Initializable, [
_Resizable,
_Themeable
], {
_initialize (config) {
console.log('Initializing my component');
this.title = config.title ?? 'Untitled';
// Component-specific initialization
}
});
{
// Create instance
const component = _MyComponent({
height: 600,
theme: 'dark',
title: 'Dashboard',
width: 800
});
// Mixins are initialized in order:
// 1. _Resizable._initialize
// 2. _Themeable._initialize
// 3. _MyComponent._initialize
}Error Handling During Initialization
import _Error from 'isotropic-error';
import _Initializable from 'isotropic-initializable';
import _make from 'isotropic-make';
const _RiskyComponent = _make(_Initializable, {
_eventInitializeError ({
data: {
error
}
}) {
// This event handler method gets executed if initialization fails.
console.error('Initialization failed:', error);
},
async _initialize (config) {
if (!config.apiKey) {
throw _Error({
message: 'API key is required',
name: 'ConfigurationError'
});
}
// Attempt to connect
const response = await fetch('https://api.example.com/connect', {
headers: {
'Authorization': `Bearer ${config.apiKey}`
}
});
if (!response.ok) {
throw _Error({
details: await response.json(),
message: `API returned ${response.status}`,
name: 'ConnectionError'
});
}
this.connection = await response.json();
}
});
{
const component = _RiskyComponent({
// Missing apiKey
});
}Selective Initialization
Sometimes you may want to skip initialization of certain parent classes or mixins:
import _Initializable from 'isotropic-initializable';
import _make from 'isotropic-make';
const _Logger = _make({
log (message) {
this.logs.push(`[${new Date().toISOString()}] ${message}`);
console.log(message);
},
_initialize() {
console.log('Logger initializing');
this.logs = [];
}
}),
_Storage = _make({
load (key) {
return this.data[key];
},
save (key, value) {
this.data[key] = value;
},
_initialize () {
console.log('Storage initializing');
this.data = {};
}
}),
// Component that uses _Logger and _Storage, but doesn't want
// to initialize _Storage (maybe to use a custom implementation)
_MyComponent = _make(_Initializable, [
_Logger,
_Storage
], {
// Override storage methods
load (key) {
this.log(`Loading ${key}`);
return this.data.get(key);
},
save (key, value) {
this.log(`Saving ${key}`);
this.data.set(key, value);
},
// Skip initialization of Storage
_doNotInitialize: Storage,
// _doNotInitialize could also be an Array or a Set
_initialize () {
console.log('MyComponent initializing');
// Custom storage implementation
this.data = new Map();
}
});
{
// Create instance
const component = _MyComponent();
// Only _Logger and _MyComponent will initialize, _Storage will be skipped
}Advanced: Completing the Lifecycle
The full lifecycle of an Initializable instance includes:
- Construction (
_initin isotropic-make) - Initialization (
_initializemethods) - Initialization complete event
- Usage
- Destruction (
destroymethod)
It's not always necessary to destroy an instance. If there isn't anything that requires explicit cleanup, the garbage collector will take care of it.
import _Initializable from 'isotropic-initializable';
import _make from 'isotropic-make';
const _Resource = _make(_Initializable, {
use () {
console.log(`Using resource: ${this.name}`);
// Use the resource
},
_destroy (...args) {
console.log(`Cleaning up resource: ${this.name}`);
closeResource(this.handle);
// Call parent destroy method
return Reflect.apply(_Initializable.prototype._destroy, this, args);
},
_init (...args) {
Reflect.apply(_Initializable.prototype._init, this, args);
console.log('Resource constructed');
return this;
},
_initialize (config) {
console.log('Resource initializing');
this.name = config.name;
this.handle = openResource(this.name);
},
_initializeComplete (config) {
console.log('Resource initialization complete');
}
});
{
// Create with automatic initialization
const resource = _Resource({
name: 'database'
});
// Use the resource
resource.use();
// Later, clean up
resource.destroy();
}API Reference
Constructor Options
_Initializable({
initialize: true // Whether to automatically initialize (default: true)
});Instance Properties
- initialized (Boolean): Whether initialization has completed successfully
Instance Methods
- initialize(...args): Start or restart initialization with the given arguments
- destroy(...args): Clean up and destroy the instance
Protected Methods
- _initialize(...args): Define initialization behavior (implemented by subclasses)
- _initializeComplete(...args): Called when initialization completes (can be overridden)
- _initializeError(): Called when initialization fails (can be overridden)
Events
- initialize: Triggered when initialization begins
- initializeComplete: Triggered when initialization completes successfully
- initializeError: Triggered if initialization fails, with error data
Integration with Other isotropic Modules
isotropic-initializable works seamlessly with other modules in the isotropic ecosystem:
- isotropic-error: Nested error reporting for initialization failures
- isotropic-later: Asynchronous utilities
- isotropic-make: Create constructor functions with inheritance and mixins
- isotropic-pubsub: Event system for the observable lifecycle
Contributing
Please refer to CONTRIBUTING.md for contribution guidelines.
Issues
If you encounter any issues, please file them at https://github.com/ibi-group/isotropic-initializable/issues
