defer Service
Interface
defer(fn, path = [])
fn
(Function
): A function that returns the value to be deferredpath
(Array
, optional): Internal parameter for tracking property access chain- Returns: A proxy object that defers execution until accessed
Description
The defer
service is a lazy evaluation utility that postpones the execution of expensive operations until their results are actually needed. It creates a proxy object that intercepts property access and method calls, building up a chain of operations that are only executed when the final result is awaited or accessed.
This service is particularly useful for:
- Lazy loading: Deferring expensive computations or I/O operations
- Dependency resolution: Resolving services that may depend on other async services
- Performance optimization: Avoiding unnecessary work when results might not be used
- Async chaining: Building complex async operation chains that execute efficiently
Key Features
- Lazy Execution: Functions are only called when results are actually needed
- Property Access: Supports chaining property access before execution
- Method Calls: Can defer method calls with arguments
- Async Support: Fully compatible with async/await patterns
- Error Handling: Provides clear error messages when trying to access undefined values
- Path Tracking: Maintains execution path for debugging
Examples
Basic Lazy Evaluation
export default {
create(){
// Defer expensive computation
return this.defer(() => {
console.log('Computing expensive value...');
return Math.random() * 1000;
});
}
}
// Usage in view
export default {
async render(){
const expensiveValue = await this.expensiveService;
// Only now is the computation performed
return this.renderHtml`<p>Value: ${expensiveValue}</p>`;
}
}
Service Dependencies
// User service that depends on session
export default {
create(){
return this.defer(async () => {
const session = await this.session;
if(!session) return;
return session.user;
});
}
}
// Usage with conditional access
export default {
async render(){
const user = await this.user;
if(user) {
return this.renderHtml`<p>Welcome, ${user.name}!</p>`;
}
return this.renderView('login');
}
}
Function Wrapping
// Defer function creation until needed
export default {
create(){
return this.defer(() => (...args) => Html.render(...args));
}
}
// Usage
export default {
async render(){
const html = await this.renderHtml`<h1>Title</h1>`;
return html;
}
}
Property Chain Access
const deferred = defer(() => ({
user: {
profile: {
name: 'John Doe',
email: 'john@example.com'
},
async getSettings(){
return { theme: 'dark', language: 'en' };
}
}
}));
// Chain property access - execution deferred until await
const userName = await deferred.user.profile.name;
// Result: "John Doe"
// Method calls in chain
const settings = await deferred.user.getSettings();
// Result: { theme: 'dark', language: 'en' }
Database Service Pattern
export default {
create(){
return this.defer(async () => {
// Only connect to database when actually needed
this.database = await Database.new(
await this.context.root.getOrCreate("databaseClient", async () =>
Client.new(await this.config.database)
),
this.context
);
// Handle multi-tenant setup
if(this.database.info.tenants){
const tenantId = this.initialParams._headers['x-tenant-id'];
if(tenantId) {
const tenant = await this.database.tenants.where({ id: tenantId }).first();
if(tenant) this.database.tenant = tenant;
}
}
return this.database;
});
}
}
Conditional Execution
// Boolean service using defer
export default {
create(){
return this.defer(async () => !!(await this.user));
}
}
// Usage
export default {
async render(){
if(await this.isSignedIn) {
return this.renderView('dashboard');
}
return this.renderView('login');
}
}
Environment-Specific Behavior
export default {
create(){
if(this.isClient) {
return this.defer(async () => {
// Client-side implementation
if(!this.context.root.hasOwnProperty('environment')){
const response = await fetch('/_pinstripe/_shell/environment.json');
this.context.root.environment = await response.json();
}
return this.context.root.environment;
});
}
return this.defer(async () => {
// Server-side implementation
return process.env.NODE_ENV ?? 'development';
});
}
}
Delegation Pattern
// Service that delegates to another service
export default {
create(){
return (...args) => this.defer(() => this.renderViews(...args));
},
async renderViews(...args){
const lastArg = args[args.length - 1];
const params = typeof lastArg == 'object' && !Array.isArray(lastArg) ? args.pop() : {};
const out = [];
for(const name of await this.matchViews(...args)){
out.push(await this.renderView(name, params));
}
return out;
}
}
Self-Reference Pattern
// Service that returns itself wrapped in defer
export default {
create(){
return this.defer(() => this);
},
build(name = 'window', options = {}){
return Bundle.create(name).build(options);
}
}
// Usage
export default {
async render(){
const bundler = await this.bundler;
const bundle = bundler.build('admin');
return this.renderHtml`<script src="${bundle.url}"></script>`;
}
}
Error Handling
The defer service provides helpful error messages when trying to access undefined values:
const deferred = defer(() => undefined);
try {
await deferred.someProperty;
} catch(error) {
// Error: Can't unwrap deferred object.someProperty (object is undefined).
}
The error message includes the full property path that was attempted, making debugging easier.
Performance Considerations
- Lazy Evaluation: Operations are only performed when results are actually needed
- Caching: The function result is cached after first execution
- Memory Efficiency: Avoids creating expensive objects until necessary
- Async Optimization: Works efficiently with async/await patterns
- Path Building: Property access chains are built efficiently using arrays
Return Value Details
The defer service returns a Proxy object that:
- Intercepts all property access and method calls
- Builds an execution path as an array
- Only executes the original function when awaited
- Supports both sync and async operations
- Maintains proper
this
context for method calls
Common Use Cases
Service Initialization
- Database connections that should only be established when needed
- Configuration loading that depends on environment
- Authentication services that check session state
Resource Management
- File system operations that may not be needed
- Network requests that should be conditional
- Expensive computations that might be cached
Dependency Injection
- Services that depend on other async services
- Circular dependency resolution
- Optional service dependencies
Performance Optimization
- Lazy loading of heavy resources
- Conditional service initialization
- Deferring expensive operations until required