Most common issue

Latency not applied

The most common issue: the original, unwrapped function is still being called instead of the wrapped version. withLatency returns a new function — you must call that new function.

Wrong

Wrapping the function but continuing to call the original.

import { withLatency } from 'slowdep'

// This wraps queryUser but the wrapped version is never used
withLatency(queryUser, 'postgres')

// Still calling the original — no latency applied
const user = await queryUser(id)
Correct

Assign the return value of withLatency and call that.

import { withLatency } from 'slowdep'

// Assign the wrapped function
const slowQuery = withLatency(queryUser, 'postgres')

// Call the wrapped version
const user = await slowQuery(id)
Class methods

Lost this context

When wrapping a class method, the wrapped function may lose its this binding. This happens because extracting a method from a class instance severs the connection to the instance. Use .bind() or an arrow function wrapper.

Wrong

Extracting an unbound method loses this.

class UserService {
  async getUser(id) {
    return this.db.query(`SELECT * FROM users WHERE id = $1`, [id])
  }
}

const svc = new UserService()
// this.db will be undefined inside the wrapped call
const slowGetUser = withLatency(svc.getUser, 'postgres')
Correct — use .bind()

Bind the method to the instance before wrapping.

const slowGetUser = withLatency(svc.getUser.bind(svc), 'postgres')

// Or use an arrow function wrapper
const slowGetUser = withLatency(
  (id) => svc.getUser(id),
  'postgres'
)

withLatencyAll handles this binding automatically when wrapping an entire object, so you only need to think about this when using withLatency on individual extracted methods.

Error handling

Wrapped function errors

When errorRate fires, the wrapped function rejects with a plain Error. The message is "Simulated transient error". The underlying function is never called in this case. Your error handling code needs to handle this rejection exactly as it would handle a real transient error.

import { withLatency } from 'slowdep'

const slowCharge = withLatency(stripe.charges.create.bind(stripe.charges), {
  p50: 200, p99: 2000, errorRate: 0.002
})

try {
  const charge = await slowCharge({ amount: 1000, currency: 'usd' })
  processCharge(charge)
} catch (err) {
  // err.message === 'Simulated transient error' when errorRate fires
  // Handle exactly as you would a real Stripe network error
  if (err.message === 'Simulated transient error' || err.type === 'StripeConnectionError') {
    await retry(() => slowCharge({ amount: 1000, currency: 'usd' }))
  }
}
TypeScript

TypeScript issues

withLatency preserves the original function's complete type signature using generics. The return type of the wrapped function is identical to the original. If TypeScript is reporting type errors with slowdep, check the following:

  • TypeScript version: slowdep requires TypeScript 4.5 or later for the generic inference to work correctly. Run tsc --version to check.
  • Strict mode: If "strict": true is set, make sure your wrapped functions have explicit return types or that TypeScript can infer them.
  • Explicit type parameter: If inference fails, provide the type parameter explicitly: withLatency<typeof myFn>(myFn, 'redis')
  • Importing types: Use import type { LatencyOptions, LatencyPreset } from 'slowdep' for type-only imports to avoid importing runtime code in type-checking contexts.
// Explicit typing when inference is ambiguous
import { withLatency } from 'slowdep'
import type { LatencyOptions } from 'slowdep'

const opts: LatencyOptions = { p50: 5, p99: 200, errorRate: 0.001 }
const slowFn = withLatency<typeof myAsyncFn>(myAsyncFn, opts)
Module format

ESM vs CommonJS

slowdep ships both ESM and CommonJS builds. The correct build is selected automatically based on your project's module format. If you see an error like "does not provide an export named 'withLatency'", the wrong build is being loaded.

  • Check your package.json: If "type": "module" is set, Node.js treats .js files as ESM. If it's absent or "commonjs", they're treated as CJS.
  • Use the right import syntax: ESM uses import { withLatency } from 'slowdep'. CJS uses const { withLatency } = require('slowdep').
  • Mixed projects: If you have a CJS project but want to use ESM imports, rename files to .mjs or set "type": "module". Do not mix require and import in the same file.
  • Bundlers: webpack, Rollup, Vite, and esbuild all handle the exports map automatically. No special configuration is needed.
Tests

Deterministic tests

slowdep uses Math.random() internally. Tests that measure timing or depend on consistent latency values will be flaky unless you mock it. Use jest.spyOn to control the random source:

import { withLatency } from 'slowdep'

let randomSpy

beforeEach(() => {
  // Lock Math.random so lognormal sampling is deterministic
  randomSpy = jest.spyOn(Math, 'random').mockReturnValue(0.5)
})

afterEach(() => {
  randomSpy.mockRestore()
})

test('returns user data', async () => {
  const slowQuery = withLatency(mockQueryUser, 'postgres')
  const user = await slowQuery(1)
  // Latency is now consistent — test will not be flaky
  expect(user.id).toBe(1)
})

See Deterministic testing for two additional approaches: mocking the whole wrapper and using an environment variable bypass.

Common mistakes

Common mistakes

Mistake — wrapping a Promise instead of a function

Calling the function before passing it to withLatency gives you a Promise, not a function. withLatency expects a function.

// Wrong: queryUser() is called immediately, returns a Promise
const slow = withLatency(queryUser(1), 'postgres')

// Correct: pass the function reference, not a call to it
const slowQuery = withLatency(queryUser, 'postgres')
const result = await slowQuery(1)
Note — wrapping synchronous functions

slowdep can wrap synchronous functions, but the added delay is still async — the result will be a Promise even if the original function returned a plain value. This is usually not what you want. Prefer wrapping async functions that already return Promises.