3.1.1 • Published 6 months ago

promise-for-wrapper v3.1.1

Weekly downloads
-
License
MIT
Repository
github
Last release
6 months ago

Promise Wrapper

A TypeScript utility library providing robust error handling and data transformation pipelines for asynchronous operations.

Core Utilities

The library provides two main utilities:

  • promiseFor: Single-step transformation with built-in error handling
  • pipeFor: Multi-step transformation pipeline with automatic error propagation

Installation

npm install promise-wrapper

Error Handling Structure

All operations use a standardized error context:

interface ErrorContext {
    message: string;
    name: string;
    stack?: string | null;
    status?: number | null;
    url?: string | null;
    method?: string | null;
    code?: string | null;
    context: string;
}

promiseFor: Single-Step Operations

Basic Structure

const [result, error] = await promiseFor(
    promise,
    postProcessor // optional
);

Simple Example

// Helper function for JSON parsing
const parseJson = async (response) => await response.json();

// Using promiseFor with post-processing
const [data, error] = await promiseFor(
    fetch('/api/users'),
    parseJson
);

or

const [data, error] = await promiseFor(
        fetch('/api/users'),
        async (res)=>res.json()
);


if (error) {
    console.error('Operation failed:', error.message);
    return;
}

pipeFor: Multi-Step Operations

Basic Structure

const [result, error] = await pipeFor(promise)
    .transform(transformFn)
    .pipe(pipelineFn)
    .transform(anotherTransform)
    .execute();

Key Concepts

  1. transform():
  • Takes exactly one argument
  • Transforms data synchronously or asynchronously
  • Automatically handles null/undefined checks
  • Returns transformed data to next step
  1. pipe():
  • Introduces new promise-based operations
  • Used when you need to make new async calls
  • Takes previous result as input
  1. execute():

Practical Example: Chained API Calls

// Helper functions
const parseJson = response => response.json();
const extractUserId = todo => todo.userId;
const extractUserProfile = user => user.profile;
const formatUserData = profile => ({
    name: profile.name,
    email: profile.email
});

// Using pipeFor with clean transformations
const [userData, error] = await pipeFor(fetch('/api/todos/1'))
    .transform(parseJson)                                    // Parse todo response
    .transform(extractUserId)                               // Get user ID
    .pipe(userId => fetch(`/api/users/${userId}`))          // New request with userId
    .transform(parseJson)                                   // Parse user response
    .transform(extractUserProfile)                          // Get profile
    .transform(formatUserData)                              // Format final data
    .execute();                                            // Run pipeline

if (error) {
    console.error('Failed to fetch user data:', error.message);
    return;
}

Automatic Null Checking Example

// Helper functions
const parseJson = response => response.json();
const extractField = data => data.requiredField;

// pipeFor handles null checks automatically
const [result, error] = await pipeFor(fetch('/api/data'))
    .transform(parseJson)
    .transform(extractField)    // No explicit null check needed
    .execute();

// If data.requiredField doesn't exist:
// result = null
// error = { message: "Cannot read property 'requiredField' of undefined", ... }

Comparative Examples

Processing Nested Data

Traditional Approach

async function getUserData() {
    try {
        const todoRes = await fetch('/api/todos/1');
        const todo = await todoRes.json();
        
        if (!todo.userId) {
            throw new Error('User ID not found');
        }
        
        const userRes = await fetch(`/api/users/${todo.userId}`);
        const user = await userRes.json();
        
        if (!user.profile) {
            throw new Error('User profile not found');
        }
        
        return user.profile;
    } catch (error) {
        console.error(error);
        return null;
    }
}

Using promiseFor

async function getUserData() {
    const [userData, error] = await promiseFor(
        fetch('/api/todos/1'),
        async (response) => {
            const todo = await response.json();
            const userRes = await fetch(`/api/users/${todo.userId}`);
            const user = await userRes.json();
            return user.profile;
        }
    );

    if (error) {
        console.error('Failed to get user data:', error);
        return null;
    }

    return userData;
}

Using pipeFor

// Helper functions make the code cleaner and more maintainable
const parseJson = response => response.json();
const extractUserId = todo => todo.userId;
const extractProfile = user => user.profile;

async function getUserData() {
    const [profile, error] = await pipeFor(fetch('/api/todos/1'))
        .transform(parseJson)
        .transform(extractUserId)
        .pipe(userId => fetch(`/api/users/${userId}`))
        .transform(parseJson)
        .transform(extractProfile)
        .execute();

    if (error) {
        console.error('Failed to get user data:', error);
        return null;
    }

    return profile;
}

Best Practices

  1. Extract Transform Functions
// Helper functions at the top level
const parseJson = response => response.json();
const extractUserId = data => data.userId;
const formatUserData = user => ({
    name: user.name,
    email: user.email
});

// Clean, readable pipeline
const [result, error] = await pipeFor(fetch('/api/users'))
    .transform(parseJson)
    .transform(extractUserId)
    .transform(formatUserData)
    .execute();
  1. Group Related Operations
const [result, error] = await pipeFor(fetch('/api/users'))
    // Data parsing group
    .transform(parseJson)
    .transform(extractUserData)
    
    // Profile enrichment group
    .pipe(user => fetch(`/api/profiles/${user.id}`))
    .transform(parseJson)
    .transform(enrichUserProfile)
    
    // Final formatting group
    .transform(formatResponse)
    .execute();
  1. Keep Transforms Simple
// Don't do this:
.transform(data => {
    // Complex logic here
    return complexOperation(data).someField;
})

// Do this instead:
const processData = data => complexOperation(data);
const extractField = result => result.someField;

.transform(processData)
.transform(extractField)
  1. Use Meaningful Names
const [userData, userError] = await pipeFor(fetch('/api/users'))
    .transform(parseJson)
    .transform(extractUserData)
    .execute();
  1. Handle Errors Appropriately
const [result, error] = await pipeFor(fetch('/api/data'))
    .transform(parseJson)
    .transform(processData)
    .execute();

if (error) {
    if (error.status === 404) {
        return defaultValue;
    }
    throw error;
}

Key Reminders

  1. transform:
  • Takes exactly one parameter
  • Returns transformed data
  • Automatically handles null/undefined
  1. pipe:
  • Use for new promise-based operations
  • Takes previous result as input
  • Returns new promise
  1. execute:
  1. Error handling:
  • Automatic null checking in transforms
  • Consistent error format
  • Pipeline stops at first error

License

MIT License

3.1.1

6 months ago

3.0.1

7 months ago

3.0.0

7 months ago

2.0.0

8 months ago

1.0.5

8 months ago

1.0.4

8 months ago

1.0.3

8 months ago

1.0.2

8 months ago

1.0.1

8 months ago

1.0.0

8 months ago