Comprehensive guide for implementing Fullstory's Asynchronous API methods (Async suffix variants) for web applications...
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:
| 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 |
The object returned from async methods:
awaited.then() chaining.catch() may not work in older browsers without Promise polyfillEvery 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', {...}) |
// Async/await pattern
const result = await FS('methodNameAsync', params)
// Promise pattern
FS('methodNameAsync', params).then((result) => {
/* handle result */
})
| Method | Resolves With |
|---|---|
getSessionAsync |
Session URL string |
setIdentityAsync |
undefined (completion signal) |
setPropertiesAsync |
undefined |
trackEventAsync |
undefined |
shutdownAsync |
undefined |
restartAsync |
undefined |
observeAsync |
Observer object with .disconnect() |
The Promise may reject when:
_fs_org)rec/settings or rec/page calls// 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:
// 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:
// 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:
// 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:
// 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:
// 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:
// 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:
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')
}
}
// 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:
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)
}
}
// 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 PromiseCORRECTED 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
)
// 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:
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()
}
// 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:
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
}
// 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'},
})
// 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)
})
// 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})
}
}
}
| 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 |
| 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 |
Symptom: await FS('methodAsync') hangs forever
Common Causes:
Solutions:
Symptom: Promise rejects with error
Common Causes:
_fs_org configurationSolutions:
Symptom: Errors not caught by .catch()
Common Causes:
Solutions:
.then() with error callbackWhen helping developers with Async Methods:
Always emphasize:
Common mistakes to watch for:
Questions to ask developers:
Best practices to recommend:
This skill document was created to help Agent understand and guide developers in implementing Fullstory's Asynchronous Methods correctly for web applications.