Skip to content

proxied

創建一個代理函數,可以攔截和修改函數的調用、參數和返回值。

基礎用法

typescript
import { proxied } from 'radash'

const add = (a: number, b: number) => a + b
const proxiedAdd = proxied(add, {
  before: (args) => console.log('Before call:', args),
  after: (result) => console.log('After call:', result)
})

console.log(proxiedAdd(2, 3)) // Before call: [2, 3] After call: 5 5

語法

typescript
function proxied<T extends any[], R>(
  fn: (...args: T) => R,
  handlers: {
    before?: (args: T) => void | T,
    after?: (result: R) => void | R,
    error?: (error: any) => void | R
  }
): (...args: T) => R

參數

  • fn (function): 要代理的函數
  • handlers (object): 代理處理器對象
    • before (function): 調用前的處理器
    • after (function): 調用後的處理器
    • error (function): 錯誤處理器

返回值

返回一個代理函數,具有與原函數相同的簽名。

示例

基本日志記錄

typescript
import { proxied } from 'radash'

const multiply = (a: number, b: number) => a * b
const loggedMultiply = proxied(multiply, {
  before: (args) => console.log(`Calling multiply with args: ${args}`),
  after: (result) => console.log(`Multiply returned: ${result}`)
})

console.log(loggedMultiply(4, 5)) // Calling multiply with args: 4,5 Multiply returned: 20 20

參數驗證

typescript
import { proxied } from 'radash'

const divide = (a: number, b: number) => a / b
const safeDivide = proxied(divide, {
  before: (args) => {
    const [a, b] = args
    if (b === 0) {
      throw new Error('Division by zero')
    }
    if (typeof a !== 'number' || typeof b !== 'number') {
      throw new Error('Arguments must be numbers')
    }
    return args
  },
  error: (error) => {
    console.error('Division error:', error.message)
    return 0
  }
})

console.log(safeDivide(10, 2)) // 5
console.log(safeDivide(10, 0)) // Division error: Division by zero 0

性能監控

typescript
import { proxied } from 'radash'

const expensiveOperation = (n: number) => {
  let result = 0
  for (let i = 0; i < n; i++) {
    result += i
  }
  return result
}

const monitoredOperation = proxied(expensiveOperation, {
  before: (args) => {
    console.time(`Operation with ${args[0]}`)
    return args
  },
  after: (result) => {
    console.timeEnd(`Operation with ${result}`)
    return result
  }
})

console.log(monitoredOperation(1000000)) // Operation with 1000000: 15.234ms 499999500000

緩存機制

typescript
import { proxied } from 'radash'

const cache = new Map()

const fetchUser = async (id: number) => {
  // 模擬API調用
  await new Promise(resolve => setTimeout(resolve, 100))
  return { id, name: `User ${id}`, email: `user${id}@example.com` }
}

const cachedFetchUser = proxied(fetchUser, {
  before: async (args) => {
    const [id] = args
    const cacheKey = `user_${id}`
    
    if (cache.has(cacheKey)) {
      console.log(`Cache hit for user ${id}`)
      return [cache.get(cacheKey)]
    }
    
    console.log(`Cache miss for user ${id}`)
    return args
  },
  after: async (result) => {
    const cacheKey = `user_${result.id}`
    cache.set(cacheKey, result)
    return result
  }
})

// 第一次調用
await cachedFetchUser(1) // Cache miss for user 1
// 第二次調用
await cachedFetchUser(1) // Cache hit for user 1

重試機制

typescript
import { proxied } from 'radash'

const unreliableApi = async (id: number) => {
  if (Math.random() < 0.7) {
    throw new Error('API Error')
  }
  return { id, data: `Data for ${id}` }
}

const retryCount = new Map()

const retryApi = proxied(unreliableApi, {
  before: (args) => {
    const [id] = args
    retryCount.set(id, (retryCount.get(id) || 0) + 1)
    return args
  },
  error: async (error) => {
    const currentRetries = Math.max(...retryCount.values())
    if (currentRetries < 3) {
      console.log(`Retrying... (attempt ${currentRetries})`)
      await new Promise(resolve => setTimeout(resolve, 1000))
      return await unreliableApi(...Array.from(retryCount.keys())[0])
    }
    console.error('Max retries reached')
    return { error: 'Max retries reached' }
  }
})

await retryApi(1)

參數轉換

typescript
import { proxied } from 'radash'

const processData = (data: any[]) => {
  return data.map(item => item * 2)
}

const validatedProcessData = proxied(processData, {
  before: (args) => {
    const [data] = args
    if (!Array.isArray(data)) {
      throw new Error('Input must be an array')
    }
    
    // 過濾非數字值
    const filteredData = data.filter(item => typeof item === 'number')
    console.log(`Filtered ${data.length - filteredData.length} non-numeric items`)
    
    return [filteredData]
  },
  after: (result) => {
    console.log(`Processed ${result.length} items`)
    return result
  }
})

console.log(validatedProcessData([1, 2, 'a', 3, 'b', 4])) // Filtered 2 non-numeric items Processed 4 items [2, 4, 6, 8]

結果轉換

typescript
import { proxied } from 'radash'

const getUserInfo = (id: number) => {
  return { id, name: `User ${id}`, email: `user${id}@example.com` }
}

const sanitizedGetUserInfo = proxied(getUserInfo, {
  after: (result) => {
    // 移除敏感信息
    const { email, ...sanitized } = result
    return sanitized
  }
})

console.log(sanitizedGetUserInfo(1)) // { id: 1, name: 'User 1' }

異步錯誤處理

typescript
import { proxied } from 'radash'

const asyncOperation = async (data: any) => {
  if (data.error) {
    throw new Error('Simulated error')
  }
  return { success: true, data }
}

const robustAsyncOperation = proxied(asyncOperation, {
  error: async (error) => {
    console.error('Operation failed:', error.message)
    return { success: false, error: error.message, fallback: 'default' }
  }
})

// 成功情況
console.log(await robustAsyncOperation({ id: 1 })) // { success: true, data: { id: 1 } }

// 失敗情況
console.log(await robustAsyncOperation({ error: true })) // Operation failed: Simulated error { success: false, error: 'Simulated error', fallback: 'default' }

權限檢查

typescript
import { proxied } from 'radash'

const userPermissions = {
  admin: ['read', 'write', 'delete'],
  user: ['read'],
  guest: []
}

const checkPermission = (user: string, action: string) => {
  const permissions = userPermissions[user as keyof typeof userPermissions] || []
  return permissions.includes(action)
}

const secureOperation = (user: string, action: string, data: any) => {
  return { user, action, data, timestamp: new Date() }
}

const securedOperation = proxied(secureOperation, {
  before: (args) => {
    const [user, action] = args
    if (!checkPermission(user, action)) {
      throw new Error(`User ${user} does not have permission to ${action}`)
    }
    console.log(`User ${user} performing ${action}`)
    return args
  },
  error: (error) => {
    console.error('Permission denied:', error.message)
    return { error: error.message, timestamp: new Date() }
  }
})

console.log(securedOperation('admin', 'write', { content: 'test' })) // User admin performing write { user: 'admin', action: 'write', data: { content: 'test' }, timestamp: ... }
console.log(securedOperation('guest', 'write', { content: 'test' })) // Permission denied: User guest does not have permission to write { error: 'User guest does not have permission to write', timestamp: ... }

數據驗證

typescript
import { proxied } from 'radash'

const createUser = (name: string, email: string, age: number) => {
  return { id: Math.random().toString(36), name, email, age }
}

const validatedCreateUser = proxied(createUser, {
  before: (args) => {
    const [name, email, age] = args
    const errors: string[] = []
    
    if (!name || name.length < 2) {
      errors.push('Name must be at least 2 characters')
    }
    
    if (!email || !email.includes('@')) {
      errors.push('Email must be valid')
    }
    
    if (age < 0 || age > 150) {
      errors.push('Age must be between 0 and 150')
    }
    
    if (errors.length > 0) {
      throw new Error(errors.join(', '))
    }
    
    return args
  },
  error: (error) => {
    console.error('Validation failed:', error.message)
    return { error: error.message, valid: false }
  }
})

console.log(validatedCreateUser('Alice', 'alice@example.com', 25)) // { id: 'abc123', name: 'Alice', email: 'alice@example.com', age: 25 }
console.log(validatedCreateUser('A', 'invalid-email', 200)) // Validation failed: Name must be at least 2 characters, Email must be valid, Age must be between 0 and 150 { error: 'Name must be at least 2 characters, Email must be valid, Age must be between 0 and 150', valid: false }

性能優化

typescript
import { proxied } from 'radash'

const heavyComputation = (n: number) => {
  let result = 0
  for (let i = 0; i < n; i++) {
    result += Math.sqrt(i)
  }
  return result
}

const optimizedComputation = proxied(heavyComputation, {
  before: (args) => {
    const [n] = args
    if (n > 1000000) {
      console.warn('Large computation detected, consider optimization')
    }
    return args
  },
  after: (result) => {
    if (result > 1000000) {
      console.log('Large result generated')
    }
    return result
  }
})

console.log(optimizedComputation(100000)) // Large computation detected, consider optimization Large result generated 666666.6666666666

調試工具

typescript
import { proxied } from 'radash'

const complexFunction = (a: number, b: string, c: boolean) => {
  return { sum: a + parseInt(b), flag: c }
}

const debugFunction = proxied(complexFunction, {
  before: (args) => {
    console.group('Function Call')
    console.log('Arguments:', args)
    console.log('Argument types:', args.map(arg => typeof arg))
    console.groupEnd()
    return args
  },
  after: (result) => {
    console.group('Function Result')
    console.log('Return value:', result)
    console.log('Return type:', typeof result)
    console.groupEnd()
    return result
  }
})

console.log(debugFunction(5, '10', true))
// Function Call
// Arguments: [5, '10', true]
// Argument types: ['number', 'string', 'boolean']
// Function Result
// Return value: { sum: 15, flag: true }
// Return type: object
// { sum: 15, flag: true }

鏈式代理

typescript
import { proxied } from 'radash'

const baseFunction = (x: number) => x * 2

const loggedFunction = proxied(baseFunction, {
  before: (args) => {
    console.log('Before:', args)
    return args
  },
  after: (result) => {
    console.log('After:', result)
    return result
  }
})

const validatedFunction = proxied(loggedFunction, {
  before: (args) => {
    const [x] = args
    if (x < 0) {
      throw new Error('Negative numbers not allowed')
    }
    return args
  },
  error: (error) => {
    console.error('Error:', error.message)
    return 0
  }
})

console.log(validatedFunction(5)) // Before: [5] After: 10 10
console.log(validatedFunction(-5)) // Error: Negative numbers not allowed 0

注意事項

  1. 處理器返回值: before 處理器可以返回修改後的參數數組
  2. 錯誤處理: error 處理器可以返回替代值或重新拋出錯誤
  3. 異步支持: 支持異步函數和異步處理器
  4. 性能影響: 代理會帶來輕微的性能開銷
  5. 類型安全: 支持TypeScript類型推斷

與其他方法的區別

  • proxied(): 創建函數代理,支持攔截和修改
  • curry(): 柯裡化函數
  • debounce(): 防抖函數
  • throttle(): 節流函數

實際應用場景

  1. 日志記錄: 記錄函數調用和返回值
  2. 性能監控: 測量函數執行時間
  3. 緩存機制: 緩存函數結果
  4. 錯誤處理: 統一處理函數錯誤
  5. 參數驗證: 驗證函數參數
  6. 權限檢查: 檢查函數調用權限
  7. 調試工具: 調試函數執行過程

Released under the MIT License.