Skip to content

map

Execute async operations on each element in an array and return an array containing all results.

Basic Usage

typescript
import { map } from 'radash'

const numbers = [1, 2, 3, 4, 5]

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

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

Syntax

typescript
function map<T, U>(
  array: readonly T[],
  fn: (item: T, index: number, array: readonly T[]) => Promise<U>
): Promise<U[]>

Parameters

  • array (readonly T[]): The array to process
  • fn (function): Async mapping function
    • item (T): Current element
    • index (number): Index of current element
    • array (readonly T[]): Original array

Return Value

Returns a Promise that resolves to the mapped array.

Examples

Basic Async Mapping

typescript
import { map } from 'radash'

const urls = [
  'https://api.example.com/users/1',
  'https://api.example.com/users/2',
  'https://api.example.com/users/3'
]

const users = await map(urls, async (url) => {
  const response = await fetch(url)
  return response.json()
})

console.log(users) // [user1, user2, user3]

Handle Object Arrays

typescript
import { map } from 'radash'

const products = [
  { id: 1, name: 'Laptop', price: 999 },
  { id: 2, name: 'Phone', price: 599 },
  { id: 3, name: 'Tablet', price: 399 }
]

const enrichedProducts = await map(products, async (product) => {
  // Simulate fetching product details
  const details = await fetch(`/api/products/${product.id}/details`)
  const productDetails = await details.json()
  
  return {
    ...product,
    ...productDetails,
    totalPrice: product.price + (productDetails.tax || 0)
  }
})

console.log(enrichedProducts)

Handle Concurrency Limits

typescript
import { map } from 'radash'

async function processWithConcurrency<T, U>(
  items: T[],
  fn: (item: T) => Promise<U>,
  concurrency: number = 3
): Promise<U[]> {
  const results: U[] = []
  
  for (let i = 0; i < items.length; i += concurrency) {
    const batch = items.slice(i, i + concurrency)
    const batchResults = await map(batch, fn)
    results.push(...batchResults)
  }
  
  return results
}

const urls = Array.from({ length: 10 }, (_, i) => `https://api.example.com/data/${i}`)

const results = await processWithConcurrency(urls, async (url) => {
  const response = await fetch(url)
  return response.json()
}, 3) // Maximum 3 concurrent requests

Handle Error Cases

typescript
import { map } from 'radash'

const numbers = [1, 2, 3, 4, 5]

const results = await map(numbers, async (num) => {
  if (num === 3) {
    throw new Error('Number 3 is not allowed')
  }
  
  await new Promise(resolve => setTimeout(resolve, 100))
  return num * 2
})

// If any operation fails, the entire map operation fails
console.log(results) // Throws error

Handle Timeout Operations

typescript
import { map } from 'radash'

async function mapWithTimeout<T, U>(
  items: T[],
  fn: (item: T) => Promise<U>,
  timeout: number
): Promise<U[]> {
  return map(items, async (item) => {
    const controller = new AbortController()
    const timeoutId = setTimeout(() => controller.abort(), timeout)
    
    try {
      const result = await Promise.race([
        fn(item),
        new Promise<never>((_, reject) => 
          setTimeout(() => reject(new Error('Timeout')), timeout)
        )
      ])
      
      clearTimeout(timeoutId)
      return result
    } catch (error) {
      clearTimeout(timeoutId)
      throw error
    }
  })
}

const urls = ['https://api.example.com/slow', 'https://api.example.com/fast']

const results = await mapWithTimeout(urls, async (url) => {
  const response = await fetch(url)
  return response.json()
}, 5000) // 5 second timeout

Handle Retry Logic

typescript
import { map } from 'radash'

async function mapWithRetry<T, U>(
  items: T[],
  fn: (item: T) => Promise<U>,
  maxRetries: number = 3
): Promise<U[]> {
  return map(items, async (item) => {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        return await fn(item)
      } catch (error) {
        if (attempt === maxRetries) {
          throw error
        }
        
        // Exponential backoff
        await new Promise(resolve => setTimeout(resolve, 1000 * attempt))
      }
    }
  })
}

const urls = ['https://api.example.com/unreliable1', 'https://api.example.com/unreliable2']

const results = await mapWithRetry(urls, async (url) => {
  const response = await fetch(url)
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`)
  }
  return response.json()
}, 3)

Handle Progress Tracking

typescript
import { map } from 'radash'

async function mapWithProgress<T, U>(
  items: T[],
  fn: (item: T, index: number) => Promise<U>,
  onProgress?: (completed: number, total: number) => void
): Promise<U[]> {
  const results: U[] = []
  
  for (let i = 0; i < items.length; i++) {
    const result = await fn(items[i], i)
    results.push(result)
    
    if (onProgress) {
      onProgress(i + 1, items.length)
    }
  }
  
  return results
}

const tasks = ['task1', 'task2', 'task3', 'task4', 'task5']

const results = await mapWithProgress(tasks, async (task, index) => {
  await new Promise(resolve => setTimeout(resolve, 1000))
  return `Completed ${task}`
}, (completed, total) => {
  console.log(`Progress: ${completed}/${total} (${Math.round(completed/total*100)}%)`)
})

Handle Conditional Mapping

typescript
import { map } from 'radash'

const users = [
  { id: 1, name: 'Alice', role: 'admin' },
  { id: 2, name: 'Bob', role: 'user' },
  { id: 3, name: 'Charlie', role: 'admin' }
]

const results = await map(users, async (user) => {
  if (user.role === 'admin') {
    // Admins need additional permission checks
    const permissions = await fetch(`/api/users/${user.id}/permissions`)
    const userPermissions = await permissions.json()
    
    return {
      ...user,
      permissions: userPermissions
    }
  } else {
    // Regular users only need basic information
    return {
      ...user,
      permissions: ['read']
    }
  }
})

console.log(results)

Handle Nested Data

typescript
import { map } from 'radash'

interface Post {
  id: number
  title: string
  authorId: number
}

interface Author {
  id: number
  name: string
  email: string
}

async function enrichPosts(posts: Post[]): Promise<(Post & { author: Author })[]> {
  return map(posts, async (post) => {
    const authorResponse = await fetch(`/api/authors/${post.authorId}`)
    const author = await authorResponse.json()
    
    return {
      ...post,
      author
    }
  })
}

const posts: Post[] = [
  { id: 1, title: 'First Post', authorId: 1 },
  { id: 2, title: 'Second Post', authorId: 2 },
  { id: 3, title: 'Third Post', authorId: 1 }
]

const enrichedPosts = await enrichPosts(posts)
console.log(enrichedPosts)

Handle Batch Operations

typescript
import { map } from 'radash'

async function processBatch<T, U>(
  items: T[],
  batchSize: number,
  fn: (batch: T[]) => Promise<U[]>
): Promise<U[]> {
  const batches: T[][] = []
  
  for (let i = 0; i < items.length; i += batchSize) {
    batches.push(items.slice(i, i + batchSize))
  }
  
  const batchResults = await map(batches, fn)
  
  return batchResults.flat()
}

const userIds = Array.from({ length: 100 }, (_, i) => i + 1)

const users = await processBatch(userIds, 10, async (batch) => {
  const response = await fetch('/api/users/batch', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ ids: batch })
  })
  
  return response.json()
})

console.log(`Processed ${users.length} users`)

Handle File Operations

typescript
import { map } from 'radash'

async function processFiles(filePaths: string[]) {
  return map(filePaths, async (filePath) => {
    const fs = await import('fs/promises')
    const content = await fs.readFile(filePath, 'utf-8')
    
    // Process file content
    const processed = content.toUpperCase()
    
    // Write processed file
    const outputPath = filePath.replace('.txt', '.processed.txt')
    await fs.writeFile(outputPath, processed)
    
    return {
      inputPath: filePath,
      outputPath,
      size: processed.length
    }
  })
}

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

const results = await processFiles(files)
console.log(results)

Notes

  1. Concurrent execution: All async operations execute concurrently
  2. Error handling: If any operation fails, the entire map operation fails
  3. Performance: Suitable for I/O-intensive operations, not CPU-intensive operations
  4. Memory: All results are stored in memory simultaneously
  5. Order: The order of the result array matches the input array order

Differences from Other Methods

  • Array.prototype.map(): Synchronous operations, doesn't support async
  • Promise.all(): Requires manually creating Promise arrays
  • map(): Concise async mapping method provided by radash

Practical Application Scenarios

  1. API calls: Batch data retrieval
  2. File processing: Batch file processing
  3. Database operations: Batch queries or updates
  4. Image processing: Batch image processing
  5. Data transformation: Batch data format conversion

Released under the MIT License.