Skip to content

mapAsync Method

The mapAsync() method transforms each item in the collection using an asynchronous callback function. It returns a Promise that resolves to a new collection containing the transformed items.

Basic Syntax

typescript
collect(items).mapAsync<U>(callback: AsyncCallback<T, U>): Promise<Collection<Awaited<U>>>

Examples

Basic Usage

typescript
import { collect } from 'ts-collect'

// Simple async transformation
const items = collect([1, 2, 3])
const doubled = await items.mapAsync(async (num) => {
  await new Promise(resolve => setTimeout(resolve, 100))
  return num * 2
})
console.log(doubled.all())  // [2, 4, 6]

// With external API
const users = collect(['user1', 'user2'])
const userDetails = await users.mapAsync(async (username) => {
  const response = await fetch(`/api/users/${username}`)
  return response.json()
})

Working with Objects

typescript
interface Product {
  id: number
  sku: string
  price: number
}

interface EnrichedProduct {
  id: number
  sku: string
  price: number
  stock: number
  details: Record<string, any>
}

const products = collect<Product>([
  { id: 1, sku: 'WIDGET-1', price: 100 },
  { id: 2, sku: 'GADGET-1', price: 200 }
])

// Enrich products with external data
const enriched = await products.mapAsync(async (product): Promise<EnrichedProduct> => {
  const [stockResponse, detailsResponse] = await Promise.all([
    fetch(`/api/stock/${product.sku}`),
    fetch(`/api/products/${product.id}/details`)
  ])

  const [stock, details] = await Promise.all([
    stockResponse.json(),
    detailsResponse.json()
  ])

  return {
    ...product,
    stock: stock.quantity,
    details
  }
})

Real-world Examples

Price Updater Service

typescript
interface PriceData {
  productId: string
  basePrice: number
  currency: string
}

interface UpdatedPrice {
  productId: string
  basePrice: number
  currency: string
  convertedPrices: Record<string, number>
  lastUpdated: Date
}

class PriceUpdaterService {
  constructor(
    private exchangeRateApi: string,
    private targetCurrencies: string[]
  ) {}

  async updatePrices(prices: Collection<PriceData>): Promise<Collection<UpdatedPrice>> {
    return prices.mapAsync(async (price) => {
      const rates = await this.fetchExchangeRates(price.currency)
      const convertedPrices: Record<string, number> = {}

      for (const currency of this.targetCurrencies) {
        if (currency !== price.currency) {
          convertedPrices[currency] = price.basePrice * rates[currency]
        }
      }

      return {
        ...price,
        convertedPrices,
        lastUpdated: new Date()
      }
    })
  }

  private async fetchExchangeRates(baseCurrency: string): Promise<Record<string, number>> {
    const response = await fetch(`${this.exchangeRateApi}?base=${baseCurrency}`)
    return response.json()
  }
}

Inventory Enrichment System

typescript
interface BaseProduct {
  sku: string
  name: string
  supplierId: string
}

interface EnrichedProduct {
  sku: string
  name: string
  supplierId: string
  stock: {
    quantity: number
    location: string
    reorderPoint: number
  }
  supplier: {
    name: string
    leadTime: number
    reliability: number
  }
  pricing: {
    cost: number
    recommendedPrice: number
    competitorPrices: number[]
  }
}

class InventoryEnricher {
  constructor(
    private stockApi: string,
    private supplierApi: string,
    private pricingApi: string
  ) {}

  async enrichProducts(products: Collection<BaseProduct>): Promise<Collection<EnrichedProduct>> {
    return products.mapAsync(async (product) => {
      const [stock, supplier, pricing] = await Promise.all([
        this.fetchStock(product.sku),
        this.fetchSupplier(product.supplierId),
        this.fetchPricing(product.sku)
      ])

      return {
        ...product,
        stock,
        supplier,
        pricing
      }
    })
  }

  private async fetchStock(sku: string) {
    const response = await fetch(`${this.stockApi}/stock/${sku}`)
    return response.json()
  }

  private async fetchSupplier(supplierId: string) {
    const response = await fetch(`${this.supplierApi}/supplier/${supplierId}`)
    return response.json()
  }

  private async fetchPricing(sku: string) {
    const response = await fetch(`${this.pricingApi}/pricing/${sku}`)
    return response.json()
  }
}

Advanced Usage

Order Processor with Rate Limiting

typescript
interface Order {
  id: string
  items: string[]
  status: 'pending' | 'processing' | 'completed'
}

interface ProcessedOrder {
  id: string
  items: string[]
  status: 'completed'
  processedAt: Date
  shippingLabel?: string
  trackingNumber?: string
}

class OrderProcessor {
  private processingDelay = 1000 // 1 second between orders
  private maxConcurrent = 5

  async processOrders(orders: Collection<Order>): Promise<Collection<ProcessedOrder>> {
    const semaphore = new Array(this.maxConcurrent).fill(Promise.resolve())
    let currentSemaphoreIndex = 0

    return orders.mapAsync(async (order) => {
      // Wait for current semaphore slot
      await semaphore[currentSemaphoreIndex]

      // Create new promise for this slot
      const processPromise = (async () => {
        try {
          // Add processing delay
          await new Promise(resolve => setTimeout(resolve, this.processingDelay))

          // Process order
          const [shippingLabel, trackingNumber] = await Promise.all([
            this.generateShippingLabel(order),
            this.generateTrackingNumber(order)
          ])

          return {
            ...order,
            status: 'completed' as const,
            processedAt: new Date(),
            shippingLabel,
            trackingNumber
          }
        } catch (error) {
          console.error(`Error processing order ${order.id}:`, error)
          throw error
        }
      })()

      // Update semaphore slot
      semaphore[currentSemaphoreIndex] = processPromise
      currentSemaphoreIndex = (currentSemaphoreIndex + 1) % this.maxConcurrent

      return processPromise
    })
  }

  private async generateShippingLabel(order: Order): Promise<string> {
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 500))
    return `SHIP-${order.id}`
  }

  private async generateTrackingNumber(order: Order): Promise<string> {
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 500))
    return `TRACK-${order.id}`
  }
}

Type Safety

typescript
interface SourceData {
  id: number
  value: string
}

interface TransformedData {
  id: number
  processed: boolean
  result: number
}

const items = collect<SourceData>([
  { id: 1, value: '100' },
  { id: 2, value: '200' }
])

// Type-safe async transformation
const transformed = await items.mapAsync(async (item): Promise<TransformedData> => {
  await new Promise(resolve => setTimeout(resolve, 100))
  return {
    id: item.id,
    processed: true,
    result: parseInt(item.value)
  }
})

// TypeScript enforces return type Promise<Collection<TransformedData>>
const result: Collection<TransformedData> = await transformed

Return Value

  • Returns a Promise that resolves to a new Collection
  • Original collection remains unchanged
  • Maintains type safety with TypeScript
  • Can be chained with other collection methods
  • Handles async/await operations
  • Preserves order of results

Common Use Cases

1. Data Enrichment

  • API integrations
  • External data fetching
  • Content enhancement
  • Detail expansion
  • Information augmentation

2. Price Processing

  • Currency conversion
  • Rate calculations
  • Price updates
  • Cost computations
  • Discount processing

3. Order Processing

  • Status updates
  • Shipping labels
  • Tracking numbers
  • Payment processing
  • Fulfillment updates

4. Inventory Management

  • Stock checks
  • Supplier queries
  • Location updates
  • Availability checks
  • Reorder processing

5. User Operations

  • Profile enrichment
  • Permission checks
  • Preference loading
  • Status updates
  • Activity tracking

6. Content Processing

  • Media processing
  • Format conversion
  • Data validation
  • Content enrichment
  • Meta data updates

7. Batch Operations

  • Bulk updates
  • Mass processing
  • Batch imports
  • Data migrations
  • Bulk exports

8. Integration Operations

  • API calls
  • Service integration
  • External validation
  • Third-party updates
  • System synchronization

9. Validation Operations

  • Async validation
  • External verification
  • Status checking
  • Compliance verification
  • Rule validation

10. Report Generation

  • Data aggregation
  • Metric collection
  • Status compilation
  • Performance analysis
  • Result accumulation

Released under the MIT License.