map()
Transform the result of a task with a mapping function.
Syntax
typescript
task.map<U>(fn: (value: T) => U | Promise<U>): FuturableTask<U>Parameters
fn
A function that transforms the task's result. Can be synchronous or asynchronous.
- Input: The resolved value of type
T - Output: The transformed value of type
U(orPromise<U>)
Return Value
A new FuturableTask<U> that resolves with the transformed value.
Description
The map() method creates a new task that applies a transformation function to the result of the original task. This is the core method for functional composition, allowing you to chain multiple transformations.
Key Characteristics
- Pure Transformation: Should not have side effects (use
tap()for that) - Type-Safe: Full TypeScript support for type transformations
- Lazy: Transformation only happens when
run()is called - Chainable: Returns a new task that can be further transformed
- Error Propagation: Errors bypass the transformation
Examples
Basic Transformation
typescript
const task = FuturableTask
.of(() => 5)
.map(x => x * 2);
const result = await task.run(); // 10Async Transformation
typescript
const task = FuturableTask
.of(() => fetch('/api/user'))
.map(response => response.json())
.map(user => user.name);
const name = await task.run(); // "John Doe"Multiple Transformations
typescript
const task = FuturableTask
.of(() => [1, 2, 3, 4, 5])
.map(arr => arr.filter(x => x > 2)) // [3, 4, 5]
.map(arr => arr.map(x => x * 2)) // [6, 8, 10]
.map(arr => arr.reduce((a, b) => a + b, 0)); // 24
const result = await task.run(); // 24Object Transformation
typescript
interface User {
id: number;
firstName: string;
lastName: string;
}
const task = FuturableTask
.of(() => fetch('/api/user'))
.map(res => res.json())
.map((user: User) => ({
id: user.id,
fullName: `${user.firstName} ${user.lastName}`
}));
const transformed = await task.run();
// { id: 1, fullName: "John Doe" }Type Conversion
typescript
// String to number
const task1 = FuturableTask
.of(() => "42")
.map(str => parseInt(str, 10));
// Number to formatted string
const task2 = FuturableTask
.of(() => 1234.56)
.map(num => num.toFixed(2))
.map(str => `$${str}`);
await task2.run(); // "$1234.56"API Response Processing
typescript
const fetchUsers = FuturableTask
.fetch('/api/users')
.map(res => res.json())
.map(users => users.filter(u => u.active))
.map(users => users.sort((a, b) => a.name.localeCompare(b.name)))
.map(users => users.slice(0, 10));
const topUsers = await fetchUsers.run();Data Enrichment
typescript
const enrichUser = FuturableTask
.of(() => fetchUser(123))
.map(user => ({
...user,
displayName: `${user.firstName} ${user.lastName}`,
age: calculateAge(user.birthDate),
isAdult: calculateAge(user.birthDate) >= 18
}));Error Handling
typescript
// map() is skipped if the task fails
const task = FuturableTask
.of(() => {
throw new Error('Failed');
})
.map(x => x * 2); // This never executes
try {
await task.run();
} catch (error) {
console.log(error.message); // "Failed"
}With Retry
typescript
const resilientTask = FuturableTask
.fetch('/api/data')
.map(res => res.json())
.retry(3)
.map(data => processData(data));Conditional Transformation
typescript
const task = FuturableTask
.of(() => fetchValue())
.map(value => value > 100 ? 'high' : 'low')
.map(category => `Category: ${category}`);Practical Examples
Form Data Processing
typescript
const submitForm = FuturableTask
.of(() => {
const formData = new FormData(form);
return Object.fromEntries(formData);
})
.map(data => ({
...data,
email: data.email.toLowerCase(),
phone: data.phone.replace(/\D/g, '')
}))
.map(data => JSON.stringify(data))
.flatMap(json =>
FuturableTask.fetch('/api/submit', {
method: 'POST',
body: json,
headers: { 'Content-Type': 'application/json' }
})
)
.map(res => res.json());Image Processing
typescript
const processImage = FuturableTask
.of(() => loadImage(url))
.map(img => resizeImage(img, 800, 600))
.map(img => applyFilter(img, 'sepia'))
.map(img => addWatermark(img, logo))
.map(img => img.toDataURL());CSV to JSON
typescript
const csvToJson = FuturableTask
.of(() => readFile('data.csv'))
.map(content => content.split('\n'))
.map(lines => lines.filter(line => line.trim()))
.map(lines => {
const headers = lines[0].split(',');
const rows = lines.slice(1);
return rows.map(row => {
const values = row.split(',');
return headers.reduce((obj, header, i) => {
obj[header] = values[i];
return obj;
}, {});
});
});Price Calculation
typescript
const calculateTotal = FuturableTask
.of(() => fetchCartItems())
.map(items => items.map(item => item.price * item.quantity))
.map(prices => prices.reduce((sum, price) => sum + price, 0))
.map(subtotal => subtotal * 1.1) // Add 10% tax
.map(total => total.toFixed(2))
.map(total => `$${total}`);Comparison with flatMap()
typescript
// map() - for value transformations
const task1 = FuturableTask
.of(() => 5)
.map(x => x * 2); // Returns 10
// flatMap() - for task transformations
const task2 = FuturableTask
.of(() => 5)
.flatMap(x => FuturableTask.of(() => x * 2)); // Returns 10
// map() with task returns nested task (wrong!)
const task3 = FuturableTask
.of(() => 5)
.map(x => FuturableTask.of(() => x * 2));
// Returns FuturableTask<FuturableTask<number>> ❌Type Transformations
typescript
// String to number
FuturableTask.of(() => "42")
.map(str => parseInt(str)); // FuturableTask<number>
// Number to boolean
FuturableTask.of(() => 42)
.map(num => num > 0); // FuturableTask<boolean>
// Object to array
FuturableTask.of(() => ({ a: 1, b: 2 }))
.map(obj => Object.values(obj)); // FuturableTask<number[]>
// Array to object
FuturableTask.of(() => [['a', 1], ['b', 2]])
.map(entries => Object.fromEntries(entries)); // FuturableTask<object>Best Practices
1. Keep Transformations Pure
typescript
// ✅ Good - pure function
.map(x => x * 2)
// ❌ Bad - side effect
.map(x => {
console.log(x); // Side effect
return x * 2;
})
// ✅ Use tap() for side effects
.tap(x => console.log(x))
.map(x => x * 2)2. Chain Multiple Maps
typescript
// ✅ Good - clear chain of transformations
FuturableTask.of(() => data)
.map(d => validate(d))
.map(d => normalize(d))
.map(d => transform(d))
// ❌ Bad - doing everything in one map
.map(d => transform(normalize(validate(d))))3. Use Type Annotations When Needed
typescript
// ✅ Explicit type for clarity
.map((user: User) => ({
id: user.id,
name: user.name
}))
// Or let TypeScript infer
.map(user => ({
id: user.id,
name: user.name
}))4. Handle Null/Undefined
typescript
// ✅ Good - handle null values
.map(value => value ?? defaultValue)
.map(value => value.toUpperCase())
// ❌ Bad - may throw
.map(value => value.toUpperCase()) // Error if nullError Handling
typescript
// Transformation throws error
const task = FuturableTask
.of(() => "not a number")
.map(str => {
const num = parseInt(str);
if (isNaN(num)) {
throw new Error('Invalid number');
}
return num;
});
const safe = task
.fallbackTo(error => {
console.error(error);
return 0;
});Performance Considerations
typescript
// ✅ Efficient - transformations only on success
FuturableTask.of(() => expensiveOperation())
.map(result => transform1(result))
.map(result => transform2(result))
.retry(3);
// Transformations only run on successful attempts
// ❌ Inefficient - computing in source
FuturableTask.of(() => {
const result = expensiveOperation();
return transform2(transform1(result));
}).retry(3);
// Transformations run on every retry attemptNotes
map()is lazy - transformation only happens whenrun()is called- If the task fails,
map()is skipped - Can be chained multiple times for complex transformations
- Use
flatMap()when the transformation returns a FuturableTask - Use
tap()for side effects - Transformations are applied in order
- Each
map()creates a new task (original is unchanged)
See Also
- flatMap() - Transform to another task
- filter() - Conditional success
- tap() - Side effects without transformation
- Functional Composition Guide
