GitHub

smartMap()

Async-aware map function with concurrency control, filtering, and error handling.

Utility Function
Async
Arrays
Syntax
smartMap(array, mapper, options)
Parameters

array
Array

The array to map over.

mapper
Function

Function to apply to each element. Can be async.

options
object
optional

Configuration options.

  • concurrency - Maximum concurrent operations (default: 5)
  • filter - Filter function to apply before mapping
  • errorHandler - Custom error handling function
  • preserveOrder - Maintain original array order (default: true)
Returns

Promise<Array>
- Promise resolving to the mapped array.

Examples

Basic Async Mapping

// Simple async transformation
const numbers = [1, 2, 3, 4, 5];

const doubled = await smartMap(numbers, async (num) => {
  await new Promise(resolve => setTimeout(resolve, 100));
  return num * 2;
});

console.log(doubled); // [2, 4, 6, 8, 10]

Concurrency Control

// Limit concurrent operations
const urls = [
  'https://api.example.com/user/1',
  'https://api.example.com/user/2',
  'https://api.example.com/user/3',
  // ... more URLs
];

const users = await smartMap(
  urls,
  async (url) => {
    const response = await fetch(url);
    return response.json();
  },
  { concurrency: 3 } // Only 3 requests at a time
);

console.log(users); // Array of user objects

With Filtering

// Filter before mapping
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const processedEvens = await smartMap(
  data,
  async (num) => {
    // Simulate async processing
    await new Promise(resolve => setTimeout(resolve, 50));
    return num * num;
  },
  {
    filter: (num) => num % 2 === 0, // Only process even numbers
    concurrency: 2
  }
);

console.log(processedEvens); // [4, 16, 36, 64, 100]

Error Handling

// Custom error handling
const mixedData = [1, 'invalid', 3, null, 5];

const results = await smartMap(
  mixedData,
  async (item) => {
    if (typeof item !== 'number') {
      throw new Error(`Invalid item: ${item}`);
    }
    return item * 2;
  },
  {
    errorHandler: (error, item, index) => {
      console.warn(`Error processing item ${index}:`, error.message);
      return null; // Return null for failed items
    }
  }
);

console.log(results); // [2, null, 6, null, 10]

File Processing

// Process files with concurrency control
const fs = require('fs').promises;
const path = require('path');

const filePaths = [
  'file1.txt',
  'file2.txt',
  'file3.txt',
  // ... more files
];

const fileContents = await smartMap(
  filePaths,
  async (filePath) => {
    try {
      const content = await fs.readFile(filePath, 'utf8');
      return {
        path: filePath,
        content: content,
        size: content.length,
        lines: content.split('\n').length
      };
    } catch (error) {
      throw new Error(`Failed to read ${filePath}: ${error.message}`);
    }
  },
  {
    concurrency: 5,
    errorHandler: (error, filePath) => {
      console.error(error.message);
      return { path: filePath, error: error.message };
    }
  }
);

console.log(fileContents);

Image Processing Pipeline

// Process images with filtering and transformation
const imageFiles = [
  'photo1.jpg', 'document.pdf', 'photo2.png', 
  'video.mp4', 'photo3.gif', 'text.txt'
];

const processedImages = await smartMap(
  imageFiles,
  async (filename) => {
    // Simulate image processing
    console.log(`Processing ${filename}...`);
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    return {
      original: filename,
      thumbnail: filename.replace(/\.[^.]+$/, '_thumb.jpg'),
      processed: true,
      timestamp: new Date().toISOString()
    };
  },
  {
    filter: (filename) => /\.(jpg|jpeg|png|gif)$/i.test(filename),
    concurrency: 3,
    preserveOrder: true
  }
);

console.log(processedImages);
// Only image files are processed, PDFs and videos are filtered out

Database Operations

// Batch database operations
const userIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const enrichedUsers = await smartMap(
  userIds,
  async (userId) => {
    // Simulate database queries
    const user = await db.users.findById(userId);
    const profile = await db.profiles.findByUserId(userId);
    const posts = await db.posts.findByUserId(userId);
    
    return {
      ...user,
      profile,
      postCount: posts.length,
      lastActivity: posts[0]?.createdAt || null
    };
  },
  {
    concurrency: 5, // Limit database connections
    errorHandler: (error, userId) => {
      console.error(`Failed to enrich user ${userId}:`, error);
      return { id: userId, error: 'Failed to load' };
    }
  }
);

console.log(enrichedUsers);

Performance Comparison

// Compare with regular Promise.all
const data = Array.from({ length: 100 }, (_, i) => i + 1);

// Regular Promise.all (all at once - can overwhelm resources)
console.time('Promise.all');
const results1 = await Promise.all(
  data.map(async (num) => {
    await new Promise(resolve => setTimeout(resolve, 100));
    return num * 2;
  })
);
console.timeEnd('Promise.all');

// smartMap with concurrency control
console.time('smartMap');
const results2 = await smartMap(
  data,
  async (num) => {
    await new Promise(resolve => setTimeout(resolve, 100));
    return num * 2;
  },
  { concurrency: 10 }
);
console.timeEnd('smartMap');

// smartMap is more resource-friendly for large datasets