Skip to main content

Managing the Context Lifecycle

Skip for now?

If you're building a single-page app and just getting started, you can skip this page. The SDK handles event batching and sending automatically. Come back here when you need to control the timing of event delivery or manage long-lived contexts.

Most of the time the SDK handles event delivery and cleanup automatically. But there are situations where you need to take control: flushing events before a page navigation, closing a context when a user logs out, or refreshing experiment data in a long-running process.

Flushing events with publish

Most SDKs batch events and send them automatically after a short delay. You don't need to call publish() for typical use cases.

Node.js, PHP, and Ruby users

The Javascript SDK running in Node.js, the PHP SDK, and the Ruby SDK do not auto-publish events. The typical pattern is to create a context at the beginning of each request, use it throughout the request to check treatments and track goals, and then call publish() or finalize() at the end before sending the response. This ensures all events are sent and the context is cleaned up before the request ends.

Even in SDKs with auto-publishing enabled, there are times when you need to make sure events are sent right now, before moving on:

  • Before a server sends its response. In server-side rendering, the context might get garbage collected before the auto-flush fires. Call publish() before sending the response to make sure exposure and goal data gets recorded.
  • Before a page navigation. In a traditional multi-page app, navigating away can interrupt the SDK's flush cycle. Call publish() before window.location changes.
  • Before a critical business event. If you need confirmation that goal data was recorded before proceeding (e.g., before showing an order confirmation), flush first.

You probably don't need publish() if:

  • You're in a single-page app where the context lives for the whole session
  • You're calling finalize() at the end anyway (it flushes automatically)
  • You're just tracking goals in a normal flow and the SDK's auto-flush is fine

Call publish() before page navigations, server responses, or any point where the process might exit before the SDK's auto-flush fires. This ensures exposure and goal data is sent to the collector before the context is lost.

// You can just publish
context.publish();

// or wait for it to finish, so if you want to
// navigate to another page without losing impressions,
// you can do that once the promise resolves.
context.publish().then(function () {
document.location.replace("another_page");
});

Closing a context with finalize

Finalizing (or closing) a context does two things: it flushes any remaining events (like publish()), and then it "seals" the context so no more events can be recorded. Any track() or publish() calls after finalize will throw an error. Calls to treatment() and variableValue() still return the correct values (assignments are computed locally), but no new exposures will be recorded.

You should finalize a context when:

  • A user logs out. You no longer know if the current user is the same person. Close the old context and create a new one with an anonymous_id.
  • Your application is shutting down. Whether it's a server process exiting, a mobile app going to background, or a component unmounting, finalize ensures nothing gets lost.
  • A server-side request is complete. Each request should create and finalize its own context so events are flushed promptly.

Finalizing (or closing) a context flushes any unpublished events and then seals it. Any track() calls after finalization will throw an error.

Call the context.finalize() method before letting a process using the SDK exit, as this method gracefully shuts down the SDK by clearing caches, and closing connections. It will also call context.publish() to flush the remaining unpublished impressions.

// You can just finalize and remove the variable reference
context.finalize();
context = null;
// finalize() returns a promise, so if you want to
// navigate to another page without losing impressions, you
// can do that once the promise resolves.
context.finalize().then(function () {
context = null;
document.location.replace("another_page");
});
caution

After finalize() is called and finishes, any subsequent invocations to the treatment() method will still return the right variant, but will result in a warning to let you know that you might be losing impressions.

Adding units after context creation

Sometimes you don't have all of a user's identifiers when you first create the context. For example, you might create a context with an anonymous_id and later learn the user_id when they log in. You can add units to an existing context without creating a new one.

context.unit("db_user_id", "1000013");

context.units({
db_user_id: "1000013",
});
caution

You cannot override a unit type that has already been set. Setting user_id twice with different values is a change of identity and will throw an exception. If the user's identity changes (for example, they log out), finalize the old context and create a new one.

Refreshing experiment data

When you create a context, the SDK fetches all running experiments and caches them in memory. But if your application runs for a long time (like a Node.js server or a single-page app that stays open all day), new experiments started after the context was created won't be picked up.

There are two ways to handle this:

Automatic refresh: Set a refresh interval when creating the context. The SDK will periodically re-fetch experiment data in the background.

Refresh interval units

The unit for the refresh interval varies by SDK. Javascript, Java, Go, PHP, and Flutter use milliseconds. Python, Ruby, and Swift use seconds. .NET uses TimeSpan.

const context = sdk.createContext(
{ units: { user_id: "user-12345" } },
{ refreshPeriod: 30000 } // 30000 ms = 30 seconds
);

Manual refresh: Call the refresh() method when you know experiment data might have changed. For example, after deploying a new experiment, or on a periodic schedule you control.

await context.refresh();

For most server-side apps where contexts are short-lived (one per request), you don't need refreshing at all. It's mainly useful for long-lived client-side contexts.

HTTP request options (Javascript SDK)

Per-request timeout

You can override the global timeout for individual HTTP requests. This is useful when a specific request (like context creation) can tolerate a different timeout than the SDK default.

const context = sdk.createContext(
request,
{
refreshInterval: 5 * 60 * 1000,
},
{
timeout: 1500,
}
);
Request cancellation

You can cancel an in-flight HTTP request using an AbortSignal. This is useful when the user navigates away before the context is ready.

The SDK bundles its own AbortController polyfill for older platforms that don't support it natively (via absmartly.AbortController). On modern browsers and Node.js 15+, the native implementation is used automatically.

const controller = new absmartly.AbortController();
const context = sdk.createContext(
request,
{
refreshInterval: 5 * 60 * 1000,
},
{
signal: controller.signal,
}
);

// Abort the request if not ready after 1500ms
const timeoutId = setTimeout(() => controller.abort(), 1500);

await context.ready();

clearTimeout(timeoutId);