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
identifymethod. 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
identifyrequest. 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:
- Timeout validation: Warns when timeout exceeds
LDClient.longTimeoutInterval(15 seconds) - Race condition prevention: Ensures completion fires exactly once
- Parallel execution: Timer and identify operation run simultaneously
- Non-canceling: The underlying identify request continues processing in the background
- 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
- Idiomatic Kotlin: Uses coroutines and Kotlin’s
Durationtype - Clean timeout handling:
withTimeoutOrNullprovides built-in timeout semantics - Non-canceling: The underlying identify request continues processing
- Return value: Returns
trueon success,falseon 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
- Promise-based: Uses standard JavaScript promises with
Promise.race - Clean timeout: Timeout and identify promises race against each other
- Error handling: Catches identify errors and returns ‘error’ result
- Non-canceling: The underlying identify request continues processing
- TypeScript support: Includes typed version for type-safe usage
Recommended timeout values
| Platform | Recommended Timeout | Notes |
|---|---|---|
| iOS | 100-500 ms | Match client-side init timeout recommendations |
| Android | 100-500 ms | Match client-side init timeout recommendations |
| JavaScript | 100-500 ms | Match 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:
-
Block SDK endpoints in your development environment:
clientsdk.launchdarkly.comclientstream.launchdarkly.comapp.launchdarkly.com
-
Call identify with your timeout wrapper
-
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.