| name | fullstory-async-methods |
| version | v2 |
| description | Comprehensive guide for implementing Fullstory's Asynchronous API methods (Async suffix variants) for web applications. Teaches proper Promise handling, await patterns, error handling, and when to use async vs fire-and-forget methods. Includes detailed good/bad examples for initialization waiting, session URL retrieval, and conditional flows to help developers handle Fullstory's asynchronous nature correctly. |
| related_skills | fullstory-observe-callbacks, fullstory-identify-users, fullstory-analytics-events, fullstory-capture-control |
Fullstory Asynchronous Methods API
Overview
Fullstory's Browser API provides asynchronous versions of all methods by appending Async to the method name. These async methods return Promise-like objects that resolve when Fullstory has started and the action completes. This is essential for:
- Initialization Waiting: Wait for Fullstory to fully bootstrap before taking actions
- Session URL Retrieval: Get the session replay URL for logging, support tickets, etc.
- Error Handling: Know if an API call succeeded or failed
- Sequential Operations: Ensure operations complete in order
- Conditional Logic: Take action based on Fullstory state
Core Concepts
Sync vs Async Methods
| Method Type | Returns | Use When |
|---|---|---|
FS('methodName') |
undefined | Fire-and-forget, don't need result |
FS('methodNameAsync') |
Promise-like | Need result, error handling, or sequencing |
Promise-like Object
The object returned from async methods:
- Can be
awaited - Supports
.then()chaining - Important:
.catch()may not work in older browsers without Promise polyfill - May reject if Fullstory fails to initialize
Available Async Methods
Every FS method has an async variant:
| Sync Method | Async Method |
|---|---|
FS('setIdentity', {...}) |
FS('setIdentityAsync', {...}) |
FS('setProperties', {...}) |
FS('setPropertiesAsync', {...}) |
FS('trackEvent', {...}) |
FS('trackEventAsync', {...}) |
FS('getSession') |
FS('getSessionAsync') |
FS('shutdown') |
FS('shutdownAsync') |
FS('restart') |
FS('restartAsync') |
FS('log', {...}) |
FS('logAsync', {...}) |
API Reference
Basic Syntax
// Async/await pattern
const result = await FS('methodNameAsync', params);
// Promise pattern
FS('methodNameAsync', params)
.then(result => { /* handle result */ });
Return Values
| Method | Resolves With |
|---|---|
getSessionAsync |
Session URL string |
setIdentityAsync |
undefined (completion signal) |
setPropertiesAsync |
undefined |
trackEventAsync |
undefined |
shutdownAsync |
undefined |
restartAsync |
undefined |
observeAsync |
Observer object with .disconnect() |
Rejection Scenarios
The Promise may reject when:
- Malformed or missing configuration (no
_fs_org) - User on unsupported browser
- Error in
rec/settingsorrec/pagecalls - Organization over quota
- Fullstory script blocked by ad blocker (may not reliably reject)
✅ GOOD IMPLEMENTATION EXAMPLES
Example 1: Get Session URL for Support
// GOOD: Get session URL for support ticket
async function attachSessionToSupportTicket(ticketId) {
try {
const sessionUrl = await FS('getSessionAsync');
// Attach to support ticket
await updateSupportTicket(ticketId, {
fullstoryUrl: sessionUrl,
attachedAt: new Date().toISOString()
});
console.log('Session attached to ticket:', sessionUrl);
return sessionUrl;
} catch (error) {
console.warn('Could not get Fullstory session:', error);
// Continue without session URL - non-critical
return null;
}
}
// Usage
document.getElementById('help-button').addEventListener('click', async () => {
const ticket = await createSupportTicket(userIssue);
await attachSessionToSupportTicket(ticket.id);
showTicketConfirmation(ticket);
});
Why this is good:
- ✅ Uses try/catch for error handling
- ✅ Gracefully handles Fullstory being unavailable
- ✅ Non-blocking failure (user can still submit ticket)
- ✅ Returns null on failure for caller to handle
Example 2: Wait for Fullstory Before Critical Actions
// GOOD: Ensure Fullstory is ready before identifying
async function initializeAnalytics(user) {
try {
// Wait for Fullstory to be ready
await FS('setIdentityAsync', {
uid: user.id,
properties: {
displayName: user.name,
email: user.email
}
});
console.log('User identified successfully');
// Now safe to track initial events
await FS('trackEventAsync', {
name: 'Session Started',
properties: {
entryPage: window.location.pathname,
referrer: document.referrer
}
});
return true;
} catch (error) {
console.error('Fullstory initialization failed:', error);
// Analytics failure shouldn't break the app
return false;
}
}
// Usage in app bootstrap
async function bootstrap() {
const user = await authenticateUser();
// Initialize analytics (don't block on failure)
initializeAnalytics(user);
// Continue app initialization
renderApp();
}
Why this is good:
- ✅ Waits for identification to complete
- ✅ Sequential: identify before tracking events
- ✅ Handles errors gracefully
- ✅ Doesn't block app on analytics failure
Example 3: Session URL in Error Reports
// GOOD: Include session URL in error logging
async function captureError(error, context = {}) {
let sessionUrl = null;
try {
// Try to get session URL, but don't let it block error reporting
sessionUrl = await Promise.race([
FS('getSessionAsync'),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 2000)
)
]);
} catch (e) {
// Session URL unavailable - continue without it
}
// Send error to monitoring service
await errorMonitor.captureException(error, {
...context,
fullstoryUrl: sessionUrl,
timestamp: new Date().toISOString()
});
// Also log to Fullstory if available
if (typeof FS !== 'undefined') {
FS('log', {
level: 'error',
msg: error.message
});
}
}
// Usage
window.addEventListener('error', (event) => {
captureError(event.error, {
source: 'window.onerror',
filename: event.filename,
lineno: event.lineno
});
});
Why this is good:
- ✅ Timeout prevents hanging on unresponsive FS
- ✅ Error reporting continues without session URL
- ✅ Enriches error context when available
- ✅ Logs error to Fullstory too
Example 4: Observer Pattern with Async
// GOOD: Set up Fullstory observers with proper cleanup
async function setupFullstoryObservers() {
const observers = [];
try {
// Observer for when Fullstory starts capturing
const startObserver = await FS('observeAsync', {
type: 'start',
callback: () => {
console.log('Fullstory started capturing');
initializeSessionTracking();
}
});
observers.push(startObserver);
// Observer for session URL availability
const sessionObserver = await FS('observeAsync', {
type: 'session',
callback: (session) => {
console.log('Session URL:', session.url);
storeSessionUrl(session.url);
}
});
observers.push(sessionObserver);
// Return cleanup function
return () => {
observers.forEach(obs => obs.disconnect());
};
} catch (error) {
console.warn('Could not set up Fullstory observers:', error);
return () => {}; // No-op cleanup
}
}
// Usage with React
function App() {
useEffect(() => {
let cleanup = () => {};
setupFullstoryObservers().then(cleanupFn => {
cleanup = cleanupFn;
});
return () => cleanup();
}, []);
return <AppContent />;
}
Why this is good:
- ✅ Proper async observer setup
- ✅ Cleanup function for component unmount
- ✅ Handles initialization failure
- ✅ Multiple observers managed together
Example 5: Conditional Feature Based on FS Status
// GOOD: Enable features only if Fullstory is working
class SessionReplayFeature {
constructor() {
this.isAvailable = false;
this.sessionUrl = null;
}
async initialize() {
try {
// Check if Fullstory is capturing
this.sessionUrl = await FS('getSessionAsync');
this.isAvailable = true;
return true;
} catch (error) {
this.isAvailable = false;
console.info('Session replay feature unavailable:', error.message);
return false;
}
}
getShareableLink() {
if (!this.isAvailable || !this.sessionUrl) {
return null;
}
return this.sessionUrl;
}
renderShareButton() {
if (!this.isAvailable) {
return null; // Don't show button if FS unavailable
}
return `<button onclick="copySessionLink()">Share Session</button>`;
}
}
// Usage
const sessionReplay = new SessionReplayFeature();
async function initializeUI() {
await sessionReplay.initialize();
if (sessionReplay.isAvailable) {
showSessionReplayUI();
}
}
Why this is good:
- ✅ Graceful degradation when FS unavailable
- ✅ Feature flag based on actual FS status
- ✅ No broken UI if FS blocked
- ✅ Clear availability check
Example 6: Sequential Operations
// GOOD: Ensure proper sequence of FS operations
async function completeCheckout(orderData) {
try {
// 1. First, ensure user is identified
await FS('setIdentityAsync', {
uid: orderData.userId,
properties: {
displayName: orderData.customerName,
email: orderData.customerEmail
}
});
// 2. Update user properties with purchase info
await FS('setPropertiesAsync', {
type: 'user',
properties: {
lifetimeValue: orderData.customerLTV,
totalOrders: orderData.customerOrderCount,
lastOrderAt: new Date().toISOString()
}
});
// 3. Track the purchase event
await FS('trackEventAsync', {
name: 'Order Completed',
properties: {
orderId: orderData.id,
revenue: orderData.total,
itemCount: orderData.items.length
}
});
// 4. Get session URL for order records
const sessionUrl = await FS('getSessionAsync');
// 5. Update order with session URL
await saveOrderSessionUrl(orderData.id, sessionUrl);
console.log('Checkout tracked successfully');
} catch (error) {
// Log but don't fail checkout
console.error('Analytics tracking failed:', error);
}
}
Why this is good:
- ✅ Operations happen in correct order
- ✅ User identified before properties set
- ✅ Event tracked after user data set
- ✅ Session URL captured at end
- ✅ Errors don't break checkout
❌ BAD IMPLEMENTATION EXAMPLES
Example 1: Blocking App on Fullstory
// BAD: Blocking application startup on Fullstory
async function startApp() {
// This will hang if Fullstory is blocked!
const sessionUrl = await FS('getSessionAsync');
// App never starts if FS fails
renderApp();
}
Why this is bad:
- ❌ App hangs if Fullstory blocked by ad blocker
- ❌ Promise may never resolve
- ❌ Critical path depends on non-critical service
- ❌ No timeout or error handling
CORRECTED VERSION:
// GOOD: Non-blocking initialization
async function startApp() {
// Start app immediately
renderApp();
// Initialize analytics separately
try {
await Promise.race([
FS('getSessionAsync'),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 5000)
)
]);
enableAnalyticsFeatures();
} catch (error) {
console.warn('Fullstory unavailable, continuing without analytics');
}
}
Example 2: Missing Error Handling
// BAD: No error handling for async call
async function trackPurchase(order) {
const sessionUrl = await FS('getSessionAsync'); // May throw!
saveSessionToOrder(order.id, sessionUrl); // Never runs if above fails
await FS('trackEventAsync', { // Also may throw
name: 'Purchase',
properties: { orderId: order.id }
});
}
Why this is bad:
- ❌ Unhandled promise rejection
- ❌ Subsequent code won't run on failure
- ❌ No graceful degradation
- ❌ Could crash in strict mode
CORRECTED VERSION:
// GOOD: Proper error handling
async function trackPurchase(order) {
let sessionUrl = null;
try {
sessionUrl = await FS('getSessionAsync');
} catch (error) {
console.warn('Could not get session URL:', error);
}
if (sessionUrl) {
saveSessionToOrder(order.id, sessionUrl);
}
try {
await FS('trackEventAsync', {
name: 'Purchase',
properties: {
orderId: order.id,
hasSessionUrl: !!sessionUrl
}
});
} catch (error) {
console.warn('Could not track purchase event:', error);
}
}
Example 3: Using .catch() Without Polyfill
// BAD: .catch() may not work in older browsers
FS('getSessionAsync')
.then(url => console.log('Session:', url))
.catch(err => console.error('Error:', err)); // May fail silently in IE11!
Why this is bad:
- ❌
.catch()not supported in browsers without Promise - ❌ Fullstory's Promise-like object may not implement catch
- ❌ Errors may go unhandled
CORRECTED VERSION:
// GOOD: Use try/catch with async/await
async function getSession() {
try {
const url = await FS('getSessionAsync');
console.log('Session:', url);
return url;
} catch (err) {
console.error('Error:', err);
return null;
}
}
// OR: Use .then() only with error callback
FS('getSessionAsync').then(
url => console.log('Session:', url),
err => console.error('Error:', err) // Second arg to .then() works
);
Example 4: Unnecessary Async Usage
// BAD: Using async when you don't need the result
async function handleButtonClick() {
// Don't need to await fire-and-forget events
await FS('trackEventAsync', {
name: 'Button Clicked',
properties: { buttonId: 'submit' }
});
// User waits unnecessarily
proceedWithAction();
}
Why this is bad:
- ❌ Adds unnecessary latency to user action
- ❌ User waits for analytics to complete
- ❌ No value from awaiting (result not used)
CORRECTED VERSION:
// GOOD: Fire-and-forget for events
function handleButtonClick() {
// Don't await - fire and forget
FS('trackEvent', {
name: 'Button Clicked',
properties: { buttonId: 'submit' }
});
// Proceed immediately
proceedWithAction();
}
Example 5: Race Condition with Async
// BAD: Race condition between identify and track
async function onLogin(user) {
// These run in parallel - trackEvent may fire before identity!
FS('setIdentityAsync', { uid: user.id });
FS('trackEventAsync', { name: 'Login' });
}
Why this is bad:
- ❌ Event may fire before identity is set
- ❌ Event could be attributed to anonymous user
- ❌ Data integrity issue
CORRECTED VERSION:
// GOOD: Sequential with proper awaiting
async function onLogin(user) {
// First identify
await FS('setIdentityAsync', {
uid: user.id,
properties: { displayName: user.name }
});
// Then track event (now properly attributed)
await FS('trackEventAsync', {
name: 'Login',
properties: { method: 'password' }
});
}
// OR: For non-critical, use sync versions (they queue properly)
function onLogin(user) {
FS('setIdentity', { uid: user.id }); // Queued first
FS('trackEvent', { name: 'Login' }); // Queued second
// Fullstory processes queue in order
}
COMMON IMPLEMENTATION PATTERNS
Pattern 1: Safe Async Wrapper
// Wrapper for safe FS async calls with timeout
async function safeFS(method, params, options = {}) {
const { timeout = 5000, fallback = null } = options;
// Check if FS exists
if (typeof FS === 'undefined') {
console.warn(`FS not available for ${method}`);
return fallback;
}
try {
const result = await Promise.race([
FS(method, params),
new Promise((_, reject) =>
setTimeout(() => reject(new Error(`FS ${method} timeout`)), timeout)
)
]);
return result;
} catch (error) {
console.warn(`FS ${method} failed:`, error.message);
return fallback;
}
}
// Usage
const sessionUrl = await safeFS('getSessionAsync', undefined, {
timeout: 3000,
fallback: null
});
await safeFS('trackEventAsync', {
name: 'Page View',
properties: { page: '/home' }
});
Pattern 2: Initialization Status Manager
// Track Fullstory initialization status
class CaptureStatusManager {
constructor() {
this.status = 'pending';
this.sessionUrl = null;
this.error = null;
this.callbacks = [];
}
async initialize() {
try {
this.sessionUrl = await FS('getSessionAsync');
this.status = 'ready';
this.callbacks.forEach(cb => cb(this.sessionUrl));
} catch (error) {
this.status = 'failed';
this.error = error;
}
return this.status === 'ready';
}
onReady(callback) {
if (this.status === 'ready') {
callback(this.sessionUrl);
} else if (this.status === 'pending') {
this.callbacks.push(callback);
}
// If failed, don't call
}
isReady() {
return this.status === 'ready';
}
getSessionUrl() {
return this.sessionUrl;
}
}
// Global instance
const fsStatus = new CaptureStatusManager();
// Initialize once
fsStatus.initialize();
// Use anywhere
fsStatus.onReady((url) => {
console.log('FS ready with session:', url);
});
Pattern 3: Analytics Queue with Fallback
// Queue analytics calls with sync fallback
class AnalyticsQueue {
constructor() {
this.useAsync = true;
this.pending = [];
}
async track(eventName, properties) {
if (this.useAsync) {
try {
await FS('trackEventAsync', {
name: eventName,
properties
});
} catch (error) {
// Fall back to sync
console.warn('Async tracking failed, using sync');
this.useAsync = false;
FS('trackEvent', { name: eventName, properties });
}
} else {
FS('trackEvent', { name: eventName, properties });
}
}
async identify(uid, properties) {
if (this.useAsync) {
try {
await FS('setIdentityAsync', { uid, properties });
} catch (error) {
this.useAsync = false;
FS('setIdentity', { uid, properties });
}
} else {
FS('setIdentity', { uid, properties });
}
}
}
WHEN TO USE ASYNC VS SYNC
Use Async When:
| Scenario | Why |
|---|---|
| Need session URL | Must wait for URL to be available |
| Error handling needed | Need to know if call failed |
| Sequential operations | Must ensure order of operations |
| Conditional logic | Need result to decide next action |
| Initialization checks | Need to know when FS is ready |
Use Sync (Fire-and-Forget) When:
| Scenario | Why |
|---|---|
| Simple event tracking | Don't need confirmation |
| Non-critical operations | Failure is acceptable |
| Performance critical paths | Don't want to add latency |
| Rapid-fire events | Queueing handles order |
| User-facing actions | Don't delay user experience |
TROUBLESHOOTING
Promise Never Resolves
Symptom: await FS('methodAsync') hangs forever
Common Causes:
- ❌ Fullstory script blocked by ad blocker
- ❌ Script failed to load
- ❌ Network issues preventing initialization
Solutions:
- ✅ Always use timeout wrapper
- ✅ Don't block critical paths
- ✅ Implement fallback behavior
Rejection Errors
Symptom: Promise rejects with error
Common Causes:
- ❌ Missing
_fs_orgconfiguration - ❌ Unsupported browser
- ❌ Organization over quota
- ❌ Configuration error
Solutions:
- ✅ Check Fullstory setup
- ✅ Verify configuration
- ✅ Handle rejections gracefully
.catch() Not Working
Symptom: Errors not caught by .catch()
Common Causes:
- ❌ Browser doesn't have native Promise
- ❌ Fullstory's Promise-like doesn't implement catch
Solutions:
- ✅ Use async/await with try/catch
- ✅ Use
.then()with error callback
KEY TAKEAWAYS FOR AGENT
When helping developers with Async Methods:
Always emphasize:
- Use timeouts to prevent hanging
- Handle rejections gracefully
- Don't block critical paths on FS
- Use try/catch, not .catch()
Common mistakes to watch for:
- Blocking app startup on FS
- Missing error handling
- Using async when sync would work
- Race conditions between calls
- .catch() without polyfill check
Questions to ask developers:
- Do you need the result?
- Is this on a critical path?
- What should happen if FS fails?
- Is proper sequencing required?
Best practices to recommend:
- Wrap in timeout for safety
- Use sync for fire-and-forget
- Graceful degradation always
- Don't let analytics break core features
REFERENCE LINKS
- Asynchronous Methods: https://developer.fullstory.com/browser/asynchronous-methods/
- Get Session Details: https://developer.fullstory.com/browser/get-session-details/
- Callbacks and Delegates: https://developer.fullstory.com/browser/fullcapture/callbacks-and-delegates/
This skill document was created to help Agent understand and guide developers in implementing Fullstory's Asynchronous Methods correctly for web applications.