Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Implementing identify timeouts

What this recipe does

This recipe shows you how to implement timeouts for identify calls in client-side and mobile SDKs. When you call identify, the SDK makes a network request to retrieve flags for the new context. By adding a timeout, your application can continue functioning even if the network request is slow or fails, rather than blocking indefinitely.

Note: Some SDKs have built-in timeout parameters for the identify method. Always prefer using native SDK timeout support when available. The iOS SDK includes native timeout support as documented below. Android and JavaScript SDKs require custom timeout implementations.

Why use this pattern

In production applications, network conditions are unpredictable. A slow or failing identify call can freeze your UI or delay critical user workflows. By implementing a timeout:

  • Improve user experience: Your app remains responsive even when LaunchDarkly services are unreachable
  • Maintain availability: Continue serving users with existing flag values instead of waiting indefinitely
  • Follow best practices: Implements the preflight checklist recommendation to not block on identify

Note: The timeout does not cancel the underlying identify request. The SDK continues processing the request in the background. If it completes after the timeout, the SDK updates flag values and fires change listeners normally.


iOS

The iOS SDK includes native timeout support for the identify method. Use the built-in timeout parameter instead of implementing a custom wrapper.

Usage

import LaunchDarkly

let client = LDClient.get()!
let userContext = LDContext(key: "user-123", kind: .user)

// Call identify with native timeout parameter
client.identify(context: userContext, timeout: 0.5) { result in
    switch result {
    case .complete:
        print("Identify completed successfully")
    case .error(let error):
        print("Identify failed with error: \(error)")
    case .timeout:
        print("Identify timed out - continuing with existing flags")
    case .shed:
        print("Identify was replaced by newer request")
    }
}

Key behaviors

The SDK’s native timeout implementation:

  1. Timeout validation: Warns when timeout exceeds LDClient.longTimeoutInterval (15 seconds)
  2. Race condition prevention: Ensures completion fires exactly once
  3. Parallel execution: Timer and identify operation run simultaneously
  4. Non-canceling: The underlying identify request continues processing in the background
  5. Consistent semantics: Matches the behavior of the SDK’s initialization timeout

Android

For Android applications, use Kotlin coroutines with withTimeoutOrNull to implement a clean, idiomatic timeout.

Implementation

import com.launchdarkly.sdk.LDContext
import com.launchdarkly.sdk.android.LDClient
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.withTimeoutOrNull
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds

/**
 * Identifies a new context with a timeout matching SDK initialization semantics
 *
 * @param context The context to identify
 * @param timeout Maximum time to wait for completion
 * @return true if identify completed successfully, false if timed out or failed
 */
suspend fun LDClient.identifyWithTimeout(
    context: LDContext,
    timeout: Duration = 500.milliseconds
): Boolean {
    val deferred = CompletableDeferred<Boolean>()
    
    // Start the identify operation
    this.identify(context) { success ->
        deferred.complete(success)
    }
    
    // Race against timeout
    return withTimeoutOrNull(timeout) {
        deferred.await()
    } ?: false // Return false on timeout
}

Usage

import kotlinx.coroutines.launch
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlin.time.Duration.Companion.milliseconds

// In a coroutine context
CoroutineScope(Dispatchers.Main).launch {
    val client = LDClient.get()
    val context = LDContext.builder("user-123")
        .build()
    
    val success = client.identifyWithTimeout(
        context = context,
        timeout = 500.milliseconds
    )
    
    if (success) {
        println("Identify completed successfully")
    } else {
        println("Identify timed out or failed - continuing with existing flags")
    }
}

Key behaviors

  1. Idiomatic Kotlin: Uses coroutines and Kotlin’s Duration type
  2. Clean timeout handling: withTimeoutOrNull provides built-in timeout semantics
  3. Non-canceling: The underlying identify request continues processing
  4. Return value: Returns true on success, false on timeout or error

JavaScript (Client-side)

For JavaScript client-side applications, use Promise.race to implement a timeout that matches the SDK’s initialization behavior.

Implementation

/**
 * Identifies a new context with a timeout matching SDK initialization semantics
 *
 * @param {import('launchdarkly-js-client-sdk').LDClient} client - The LaunchDarkly client
 * @param {import('launchdarkly-js-client-sdk').LDContext} context - The context to identify
 * @param {number} timeout - Maximum time to wait in milliseconds
 * @returns {Promise<'complete' | 'timeout'>} Result of the identify operation
 */
async function identifyWithTimeout(client, context, timeout = 500) {
  // Create timeout promise
  const timeoutPromise = new Promise((resolve) => {
    setTimeout(() => resolve('timeout'), timeout);
  });

  // Create identify promise
  const identifyPromise = client
    .identify(context)
    .then(() => 'complete')
    .catch(() => 'error');

  // Race them
  return Promise.race([identifyPromise, timeoutPromise]);
}

Usage

import * as LDClient from 'launchdarkly-js-client-sdk';

const client = LDClient.initialize('YOUR_CLIENT_SIDE_ID', {
  key: 'anonymous-user',
});

// Wait for initial initialization
await client.waitForInitialization();

// Later, when identifying a new user
const newContext = {
  kind: 'user',
  key: 'user-123',
  email: 'user@example.com',
};

const result = await identifyWithTimeout(client, newContext, 500);

switch (result) {
  case 'complete':
    console.log('Identify completed successfully');
    break;
  case 'error':
    console.log('Identify failed with error');
    break;
  case 'timeout':
    console.log('Identify timed out - continuing with existing flags');
    break;
}

TypeScript version

import * as LDClient from 'launchdarkly-js-client-sdk';

type IdentifyResult = 'complete' | 'error' | 'timeout';

async function identifyWithTimeout(
  client: LDClient.LDClient,
  context: LDClient.LDContext,
  timeout: number = 500
): Promise<IdentifyResult> {
  const timeoutPromise = new Promise<'timeout'>((resolve) => {
    setTimeout(() => resolve('timeout'), timeout);
  });

  const identifyPromise = client
    .identify(context)
    .then((): IdentifyResult => 'complete')
    .catch((): IdentifyResult => 'error');

  return Promise.race([identifyPromise, timeoutPromise]);
}

Key behaviors

  1. Promise-based: Uses standard JavaScript promises with Promise.race
  2. Clean timeout: Timeout and identify promises race against each other
  3. Error handling: Catches identify errors and returns ‘error’ result
  4. Non-canceling: The underlying identify request continues processing
  5. TypeScript support: Includes typed version for type-safe usage

PlatformRecommended TimeoutNotes
iOS100-500 msMatch client-side init timeout recommendations
Android100-500 msMatch client-side init timeout recommendations
JavaScript100-500 msMatch client-side init timeout recommendations

These values align with the preflight checklist recommendations for client-side initialization timeouts.


Testing your implementation

To verify your timeout implementation works correctly:

  1. Block SDK endpoints in your development environment:

    • clientsdk.launchdarkly.com
    • clientstream.launchdarkly.com
    • app.launchdarkly.com
  2. Call identify with your timeout wrapper

  3. Verify behavior:

    • Application continues functioning after the timeout period
    • Timeout handler is called
    • No crashes or UI blocking

For more testing strategies, see Emulating LaunchDarkly Downtime.