Object.prototype.pipe() - Universal Method Chaining in JavaScript
Status
- Stage: -1 (Strawperson)
- Platform: es.discourse.group
- Category: Proposals
Author
Fabio Roldan
Synopsis
A proposal to add a .pipe()
method to Object.prototype
enabling universal method chaining across all JavaScript values.
Motivation
JavaScript lacks a universal way to chain operations that works consistently across all types. While method chaining is common with objects and arrays, working with primitive values often requires nested function calls or temporary variables. This proposal aims to provide a uniform interface for value transformation that works with all JavaScript values.
Why Not Pipeline Operator?
The existing pipeline operator proposal (|>
) introduces new syntax and requires parser changes. In contrast, .pipe()
:
- Uses familiar method syntax
- Works with existing code patterns
- Can be polyfilled
- Integrates naturally with existing method chains
Proposal
Object.prototype.pipe = function(callback) {
if (typeof callback !== 'function') return this;
const value = this instanceof Object &&
(this instanceof Number || this instanceof Boolean ||
this instanceof BigInt || this instanceof Symbol ||
this instanceof String)
? this.valueOf()
: this;
const result = callback.call(this, value);
return result !== undefined ? result : value;
};
Examples and Best Practices
Correct Usage Patterns
// String processing
"hello world "
.trim()
.pipe(str => `*${str}*`);
.toUpperCase()
// Array transformations
[1, 2, 3, 4, 5]
.filter(n => n > 2)
.pipe(arr => ({ sum: arr.reduce((a, b) => a + b, 0) }))
.pipe(({ sum }) => sum.toString())
.padStart(5, '0');
// Object manipulation
const user = { name: 'John', age: 30 }
.pipe(obj => ({ ...obj, role: 'admin' }))
.pipe(console.log)
.pipe(({ role }) => role)
.toUpperCase();
Anti-patterns to Avoid
// Don't use pipe for simple native methods
value.pipe(str => str.toUpperCase()) // Instead: value.toUpperCase()
value.pipe(arr => arr.reverse()) // Instead: value.reverse()
// Don't chain multiple simple operations
value
.pipe(x => x + 1)
.pipe(x => x * 2) // Instead: value.pipe(x => (x + 1) * 2)
Complex Data Processing
const processData = data => data
.filter(isValid)
.map(getValue)
.pipe(values => ({
total: values.reduce((sum, val) => sum + val, 0),
count: values.length,
items: values
}))
.pipe(stats => ({
...stats,
average: stats.total / stats.count
}));
Async Operations
const fetchAndProcess = async (data) => data
.pipe(items => JSON.stringify(items))
.pipe(async body => {
const response = await fetch('/api/process', {
method: 'POST',
body
});
return response.json();
})
.pipe(({ data, metadata }) => ({
processed: data,
timestamp: metadata.timestamp
}));
Type-Safe Examples (TypeScript)
interface PipeableObject {
pipe<T, R>(this: T, fn: (value: T) => R): R;
}
// Type inference examples
const processUser = (user: User) => user
.pipe(({ name, age }) => ({
displayName: name.toUpperCase(),
isAdult: age >= 18
}))
.pipe(({ displayName, isAdult }) => ({
message: `${displayName} is ${isAdult ? 'an adult' : 'underage'}`
}));
Debugging
const debug = context => value => {
console.log(`[${context}]`, {
type: typeof value,
value,
timestamp: new Date().toISOString()
});
return value;
};
// Usage in data processing
const processWithDebug = data => data
.pipe(debug('Initial data'))
.filter(isValid)
.pipe(debug('After validation'))
.pipe(transform)
.pipe(debug('Final result'));
Security and Error Handling
const processSecurely = userInput => userInput
.pipe(input => {
if (typeof input !== 'string') {
throw new TypeError('Expected string input');
}
return input;
})
.pipe(sanitizeInput)
.pipe(value => {
try {
return JSON.parse(value);
} catch (e) {
throw new ValidationError('Invalid JSON');
}
})
.pipe(validateSchema)
.pipe(auditLog);
Implementation Considerations
Performance
- Minimal overhead compared to traditional method chaining
- No parser modifications required
- JIT-friendly implementation
- Optimizable for common patterns
Edge Cases
// Primitive handling
undefined.pipe(x => x) // returns undefined
null.pipe(x => x) // returns null
new Number(42).pipe(x => x) // returns 42
// Preserving this context
const obj = {
value: 42,
double() { return this.value * 2; }
};
obj.pipe(function() {
return this.double();
}); // returns 84
Framework Integration
// React Component
const DataProcessor = ({ rawData }) => {
return rawData
.pipe(normalizeData)
.pipe(computeStats)
.pipe(formatForDisplay)
.map(item => (
<DataItem key={item.id} {...item} />
));
};
// Vue Component
export default {
computed: {
processedData() {
return this.rawData
.pipe(this.normalize)
.pipe(this.transform);
}
}
}
Future Considerations
- Promise handling optimizations
- Iterator/Generator support
- Performance improvements
- Additional debugging utilities
Discussion Points
Benefits
- Universal chaining across all types
- No new syntax required
- Natural integration with existing methods
- Improved debugging capabilities
- Easy to polyfill and test
Considerations
- Impact of extending Object.prototype
- Performance implications
- TypeScript integration
- Security considerations
Feedback Requested
- Is the syntax intuitive and clear?
- Are the use cases compelling?
- Are there edge cases we haven't considered?
- Concerns about extending Object.prototype?
- Implementation suggestions?
- Ideas for additional features?
Next Steps
- Gather community feedback
- Refine the proposal based on discussions
- Identify potential TC39 champions
- Prepare formal TC39 presentation
References
Please share your thoughts, concerns, and suggestions in the comments below.