Skip to content

reduce

Execute async reduction operations on each element in an array and accumulate results.

Basic Usage

typescript
import { reduce } from 'radash'

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

const sum = await reduce(numbers, async (acc, num) => {
  await new Promise(resolve => setTimeout(resolve, 100))
  return acc + num
}, 0)

console.log(sum) // 15

Syntax

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

Parameters

  • array (readonly T[]): The array to process
  • fn (function): Async reduction function
    • acc (U): Accumulated value
    • item (T): Current element
    • index (number): Index of current element
    • array (readonly T[]): Original array
  • initial (U): Initial value

Return Value

Returns a Promise that resolves to the final accumulated result.

Examples

Basic Async Reduction

typescript
import { reduce } from 'radash'

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

const sum = await reduce(numbers, async (acc, num) => {
  await new Promise(resolve => setTimeout(resolve, 100))
  return acc + num
}, 0)

console.log(sum) // 15

Handle Object Arrays

typescript
import { reduce } from 'radash'

const users = [
  { id: 1, name: 'Alice', age: 25 },
  { id: 2, name: 'Bob', age: 30 },
  { id: 3, name: 'Charlie', age: 35 }
]

const userStats = await reduce(users, async (acc, user) => {
  // Simulate async operation
  await new Promise(resolve => setTimeout(resolve, 50))
  
  return {
    totalAge: acc.totalAge + user.age,
    names: [...acc.names, user.name],
    count: acc.count + 1
  }
}, { totalAge: 0, names: [], count: 0 })

console.log(userStats)
// { totalAge: 90, names: ['Alice', 'Bob', 'Charlie'], count: 3 }

Handle API Call Accumulation

typescript
import { reduce } from 'radash'

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

const userData = await reduce(userIds, async (acc, userId) => {
  const response = await fetch(`/api/users/${userId}`)
  const user = await response.json()
  
  return {
    users: [...acc.users, user],
    totalAge: acc.totalAge + user.age,
    averageAge: (acc.totalAge + user.age) / (acc.users.length + 1)
  }
}, { users: [], totalAge: 0, averageAge: 0 })

console.log(userData)

Handle File Processing

typescript
import { reduce } from 'radash'

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

const fileStats = await reduce(filePaths, async (acc, filePath) => {
  const fs = await import('fs/promises')
  const content = await fs.readFile(filePath, 'utf-8')
  
  return {
    totalSize: acc.totalSize + content.length,
    fileCount: acc.fileCount + 1,
    averageSize: (acc.totalSize + content.length) / (acc.fileCount + 1),
    files: [...acc.files, { path: filePath, size: content.length }]
  }
}, { totalSize: 0, fileCount: 0, averageSize: 0, files: [] })

console.log(fileStats)

Handle Database Queries

typescript
import { reduce } from 'radash'

const categories = ['electronics', 'books', 'clothing']

const categoryStats = await reduce(categories, async (acc, category) => {
  const response = await fetch(`/api/products?category=${category}`)
  const products = await response.json()
  
  const categoryTotal = products.reduce((sum: number, product: any) => 
    sum + product.price, 0
  )
  
  return {
    totalRevenue: acc.totalRevenue + categoryTotal,
    categoryCounts: {
      ...acc.categoryCounts,
      [category]: products.length
    },
    averagePrice: (acc.totalRevenue + categoryTotal) / 
      (Object.values(acc.categoryCounts).reduce((a: number, b: number) => a + b, 0) + products.length)
  }
}, { totalRevenue: 0, categoryCounts: {}, averagePrice: 0 })

console.log(categoryStats)

Handle Complex Aggregation

typescript
import { reduce } from 'radash'

interface Order {
  id: number
  customerId: number
  amount: number
  date: string
}

const orders: Order[] = [
  { id: 1, customerId: 1, amount: 100, date: '2023-01-01' },
  { id: 2, customerId: 1, amount: 200, date: '2023-01-02' },
  { id: 3, customerId: 2, amount: 150, date: '2023-01-01' },
  { id: 4, customerId: 2, amount: 300, date: '2023-01-03' }
]

const orderAnalytics = await reduce(orders, async (acc, order) => {
  // Simulate async processing
  await new Promise(resolve => setTimeout(resolve, 10))
  
  const customerId = order.customerId
  const existingCustomer = acc.customers.find(c => c.id === customerId)
  
  if (existingCustomer) {
    existingCustomer.totalSpent += order.amount
    existingCustomer.orderCount += 1
  } else {
    acc.customers.push({
      id: customerId,
      totalSpent: order.amount,
      orderCount: 1
    })
  }
  
  return {
    ...acc,
    totalRevenue: acc.totalRevenue + order.amount,
    totalOrders: acc.totalOrders + 1
  }
}, { customers: [], totalRevenue: 0, totalOrders: 0 })

console.log(orderAnalytics)

Handle Error Recovery

typescript
import { reduce } from 'radash'

const urls = [
  'https://api.example.com/data1',
  'https://api.example.com/data2',
  'https://api.example.com/data3'
]

const results = await reduce(urls, async (acc, url) => {
  try {
    const response = await fetch(url)
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`)
    }
    const data = await response.json()
    
    return {
      successful: [...acc.successful, data],
      failed: acc.failed,
      totalProcessed: acc.totalProcessed + 1
    }
  } catch (error) {
    return {
      successful: acc.successful,
      failed: [...acc.failed, { url, error: error.message }],
      totalProcessed: acc.totalProcessed + 1
    }
  }
}, { successful: [], failed: [], totalProcessed: 0 })

console.log(results)

Handle Conditional Accumulation

typescript
import { reduce } from 'radash'

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

const numberStats = await reduce(numbers, async (acc, num) => {
  // Simulate async validation
  await new Promise(resolve => setTimeout(resolve, 10))
  
  if (num % 2 === 0) {
    return {
      ...acc,
      evenSum: acc.evenSum + num,
      evenCount: acc.evenCount + 1
    }
  } else {
    return {
      ...acc,
      oddSum: acc.oddSum + num,
      oddCount: acc.oddCount + 1
    }
  }
}, { evenSum: 0, evenCount: 0, oddSum: 0, oddCount: 0 })

console.log(numberStats)
// { evenSum: 30, evenCount: 5, oddSum: 25, oddCount: 5 }

Handle State Management

typescript
import { reduce } from 'radash'

interface Action {
  type: string
  payload?: any
}

const actions: Action[] = [
  { type: 'ADD_USER', payload: { id: 1, name: 'Alice' } },
  { type: 'ADD_USER', payload: { id: 2, name: 'Bob' } },
  { type: 'REMOVE_USER', payload: { id: 1 } },
  { type: 'UPDATE_USER', payload: { id: 2, name: 'Bob Updated' } }
]

const finalState = await reduce(actions, async (state, action) => {
  // Simulate async state processing
  await new Promise(resolve => setTimeout(resolve, 10))
  
  switch (action.type) {
    case 'ADD_USER':
      return {
        ...state,
        users: [...state.users, action.payload]
      }
    case 'REMOVE_USER':
      return {
        ...state,
        users: state.users.filter(user => user.id !== action.payload.id)
      }
    case 'UPDATE_USER':
      return {
        ...state,
        users: state.users.map(user => 
          user.id === action.payload.id 
            ? { ...user, ...action.payload }
            : user
        )
      }
    default:
      return state
  }
}, { users: [] })

console.log(finalState)

Handle Batch Processing

typescript
import { reduce } from 'radash'

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

const batchResults = await reduce(items, async (acc, item) => {
  // Process in batches of 10
  const batch = [...acc.currentBatch, item]
  
  if (batch.length === 10 || item === items[items.length - 1]) {
    // Process batch
    const batchSum = batch.reduce((sum, num) => sum + num, 0)
    
    return {
      processedBatches: [...acc.processedBatches, batchSum],
      currentBatch: [],
      totalProcessed: acc.totalProcessed + batch.length
    }
  }
  
  return {
    ...acc,
    currentBatch: batch
  }
}, { processedBatches: [], currentBatch: [], totalProcessed: 0 })

console.log(batchResults)

Notes

  1. Sequential execution: Operations are executed sequentially, not in parallel
  2. Error handling: If any operation fails, the entire reduce operation fails
  3. Performance: Suitable for operations that depend on previous results
  4. Memory: Only stores the accumulated result, not all intermediate results
  5. Order: Operations are executed in the order of the input array

Differences from Other Functions

  • Array.prototype.reduce(): Synchronous operations, doesn't support async
  • reduce(): radash's async reduction method
  • map(): Processes all elements independently, while reduce accumulates

Practical Application Scenarios

  1. Data aggregation: Accumulate data from multiple sources
  2. State management: Build complex state from actions
  3. File processing: Accumulate file statistics
  4. API processing: Build complex objects from API responses
  5. Batch operations: Process items in batches with accumulation

Released under the MIT License.