Node.js SDK

This guide provides detailed information about our Node.js SDK. All of our SDKs are open source -- head to our Node.js SDK GitHub repository to understand more.

Initialization

Set up Split in your code base with two simple steps:

1. Import the SDK into your project

The SDK is published using npm, so it's fully integrated with your workflow:

npm install --save @splitsoftware/splitio@10.5.0

2. Instantiate the SDK and create a new Split client.

var SplitFactory = require('@splitsoftware/splitio').SplitFactory;

var factory = SplitFactory({
  core: {
    authorizationKey: 'YOUR_API_KEY'
  }
});

var client = factory.client();
import { SplitFactory } from '@splitsoftware/splitio';

const factory: SplitIO.ISDK = SplitFactory({ 
  core: {
    authorizationKey: 'YOUR_API_KEY'
  }
});

const client: SplitIO.IClient = factory.client();

Updating to v10

We changed our module system to ES Modules and now we are exposing an object with a SplitFactory property. That property will point to the same factory function that we were returning in the previous versions.
Please take a look at the snippet above to see the code.

Running in the Browser?

*Please refer to our Javascript SDK Setup as the bundles are slightly different.

Notice for TypeScript

With the SDK package on NPM, you will get the SplitIO namespace which contains useful types and interfaces for you to use.
Feel free to dive into the declaration files if intellisense is not enough!

We recommend keeping only one instance of the client at all times (Singleton pattern) and reusing it throughout your application.

Include the API key for the environment you are setting up the SDK for. The API key is available under your Organization Settings page, under the APIs tab. The API key is of type sdk.

Use the Split client to evaluate treatments.

Using the SDK

Basic Usage

When the SDK is instantiated, it kicks off background jobs to update an in-memory cache with small amounts of data fetched from Split servers. This process can take up to a few hundred milliseconds. While the SDK is in this intermediate state, if it is asked to evaluate which treatment to show to the logged in customer for a specific Split, it may not have data necessary to run the evaluation. In this circumstance, the SDK does not fail, rather it will return the control treatment.

To make sure the SDK is properly loaded before asking it for a treatment you need to block until the SDK is ready as shown below. We set the client to listen for the SDK_READY event triggered by the SDK before asking for an evaluation.

Once the SDK_READY event fires, you can use the getTreatment method to return the proper treatment based on the SPLIT_NAME you pass and the CUSTOMER_ID you passed when instantiating the SDK.

From there, you simply need to use an if-else-if block as shown below and plug the code in for the different treatments that you defined in the Split UI. Make sure to remember the final else branch in your code to handle the client returning control.

client.on(client.Event.SDK_READY, function() {
  var treatment = client.getTreatment('CUSTOMER_ID', 'SPLIT_NAME');

  if (treatment == 'on') {
      // insert code here to show on treatment
  } else if (treatment == 'off') {
      // insert code here to show off treatment
  } else {
      // insert your control treatment code here
  }
});
client.on(client.Event.SDK_READY, () => {
  const treatment: SplitIO.Treatment =
    client.getTreatment('CUSTOMER_ID', 'SPLIT_NAME');

  if (treatment == 'on') {
      // insert code here to show on treatment
  } else if (treatment == 'off') {
      // insert code here to show off treatment
  } else {
      // insert your control treatment code here
  }
});

Multiple evaluations at once

In some instances, you may want to evaluate treatments for multiple splits at once, you can use the GetTreatments method of the Split client to do this. Simply pass a list of the split names you want treatments for and the method will return an object with the** results.

var splitNames = ['SPLIT_NAME_1', 'SPLIT_NAME_2'];

var treatments = client.getTreatments('CUSTOMER_ID', splitNames);

// treatments will have the following form: 
// {
//   SPLIT_NAME_1: 'on',
//   SPLIT_NAME_2: 'visa'
// }
const splitNames = ['SPLIT_NAME_1', 'SPLIT_NAME_2'];

const treatments: SplitIO.Treatments = client.getTreatments(
  'CUSTOMER_ID', 
  splitNames
);

// treatments will have the following form: 
// {
//   SPLIT_NAME_1: 'on',
//   SPLIT_NAME_2: 'visa'
// }

You can also use the Split Manager if you are looking to get all of your treatments at once.

Working with Sync and Async storages

If your code runs with both types of storage, please read Working with both Sync and Async storages.

Attributes Syntax

In order to Target Based on Custom Attributes, the SDK's getTreatment method needs be passed an attribute map at runtime.

In the example below, we are rolling out a Split to users. The provided attributes - plan_type, registered_date, permissions, paying_customer, and deal_size - are passed to the getTreatment call. These attributes will be compared and evaluated against attributes used in the Rollout plan as defined in the Split Web console to decide whether to show the 'on' or 'off' treatment to this account.

The getTreatment method supports five types of Attributes: Strings, Numbers, Dates, Booleans, and Sets. The proper data type and syntax for each are:

  • Strings: These attributes should be of type String
  • Numbers: These attributes should be of type Number
  • Dates: These attributes should be of type Date and should be expressed in milliseconds since epoch. Note: Milliseconds since epoch is expressed in UTC. If your date or date-time combination is in a different timezone, first convert it to UTC, then transform it to milliseconds since epoch.
  • Booleans: These attributes should be of type Boolean
  • Sets: These attributes should be of type Array
var attributes = {
  // date attributes are handled as `millis since epoch`
  registered_date: new Date('YYYY-MM-DDTHH:mm:ss.sssZ').getTime(),
  // this string will be compared against a list called `plan_type`
  plan_type: 'growth',
  // this number will be compared against a const value called `deal_size`
  deal_size: 10000,
  // this boolean will be compared against a const value called `paying_customer`
  paying_customer: true,
  // this array will be compared against a set called `permissions`
  permissions: [‘read’, ‘write’]
};

var treatment = client.getTreatment('CUSTOMER_ID', 'SPLIT_NAME', attributes);

if (treatment === 'on') {
  // insert on code here
} else if (treatment === 'off') {
  // insert off code here
} else {
  // insert control code here
}
const attributes: SplitIO.Attributes = {
  // date attributes are handled as `millis since epoch`
  registered_date: new Date('YYYY-MM-DDTHH:mm:ss.sssZ').getTime(),
  // this string will be compared against a list called `plan_type`
  plan_type: 'growth',
  // this number will be compared agains a const value called `deal_size`
  deal_size: 10000,
  // this array will be compared against a set called `permissions`
  permissions: [‘read’, ‘write’]
};

const treatment: SplitIO.Treatment =
  client.getTreatment('CUSTOMER_ID', 'SPLIT_NAME', attributes);

if (treatment === 'on') {
  // insert on code here
} else if (treatment === 'off') {
  // insert off code here
} else {
  // insert control code here
}

You can pass your attributes in the exact same way to client.getTreatments method.

Track

The track method allows you to record any actions your customers perform. Each action is known as an event and corresponds to an event type. Calling track through one of our SDKs or via API is the first step to getting experimentation data into Split and allows you to measure the impact of your Splits on your users' actions and metrics.

Learn more about using track events in splits.

In the examples below you'll see that the .track() method can take up to four arguments. The proper data type and syntax for each are:

  • CUSTOMER_ID: Represents the customer_id used in your getTreatment call and firing this track event. - Expected Data Type: String
  • TRAFFIC_TYPE: Represents the traffic type of the customer id in the track call. - Expected Data Type: String - You can only pass values that match the names of Traffic Types that you have defined in your instance of Split.
  • EVENT_TYPE: Represents the event type that this event should correspond to. - Expected Data Type: String - Full requirements on this argument are:
    • 63 characters or less
    • Starts with a letter or number
    • Contains only letters, numbers, hyphen, underscore, or period.
    • This is the regular expression we use to validate the value: [a-zA-Z0-9][-_\.a-zA-Z0-9]{0,62}
  • VALUE: (optional) Represents the value to be used in creating the metric. This field can be sent in as null or 0 if you intend to purely utilize the count function when creating a metric. - Expected Data Type: Integer or Float
// If you would like to send an event without a value
var queuedPromise = client.track('CUSTOMER_ID', 'TRAFFIC_TYPE', 'EVENT_TYPE');
// Example
var queuedPromise = client.track("john@doe.com", "user", "page_load_time");

// If you would like to associate a value with an event
var queuedPromise = client.track('CUSTOMER_ID', 'TRAFFIC_TYPE', 'EVENT_TYPE', eventValue);
// Example
var queuedPromise = client.track("john@doe.com", "user", "page_load_time", 83.334);

// The track function returns a promise which will resolve to a boolean, indicating if the event was correctly queued or not.
queuedPromise.then(function(queued) {
  console.log(queued ? 'Successfully queued event' : 'Failed to queue event');
});
// If you would like to send an event without a value
const queuedPromise: Promise<boolean> = client.track('CUSTOMER_ID', 'TRAFFIC_TYPE', 'EVENT_TYPE');
// Example
const queuedPromise: Promise<boolean> = client.track('john@doe.com', 'user', 'page_load_time');

// If you would like to associate a value with an event
const queuedPromise: Promise<boolean> = client.track('CUSTOMER_ID', 'TRAFFIC_TYPE', 'EVENT_TYPE', eventValue);
// Example
const queuedPromise: Promise<boolean> = client.track('john@doe.com', 'user', 'page_load_time', 83.334);

// The track function returns a promise which will resolve to a boolean, indicating if the event was correctly queued or not.
queuedPromise.then(queued => {
  console.log(queued ? 'Successfully queued event' : 'Failed to queue event');
});

Configuration

The SDK has a number of knobs that can be turned to configure it for performance. Each knob is tuned to a reasonable default, however, you can provide an override value while instantiating the SDK. Below are the parameters available for configuration:

Configuration
Description
Defaut value

scheduler.featuresRefreshRate

The SDK polls Split servers for changes to feature roll-out plans. This parameter controls this polling period in seconds.

30

scheduler.segmentsRefreshRate

The SDK polls Split servers for changes to segment definitions. This parameter controls this polling period in seconds.

60

scheduler.metricsRefreshRate

The SDK sends diagnostic metrics to Split servers. This parameters controls this metric flush period in seconds.

60

scheduler.impressionsRefreshRate

The SDK sends information on who got what treatment at what time back to Split servers to power analytics. This parameter controls how often this data is sent to Split servers. The parameter should be in seconds.

60

scheduler.eventsPushRate

The SDK will sends tracked events to Split servers. This setting controls that flushing rate in seconds.

60

scheduler.eventsQueueSize

The max amount of events we will queue. If the queue is full, the SDK will flush the events and reset the timer.

500

core.labelsEnabled

Disable labels from being sent to Split backend. Labels may contain sensitive information.

true

startup.requestTimeoutBeforeReady

Time to wait for a request before the SDK is ready. If this time expires, Node SDK will retry 'retriesOnFailureBeforeReady' times before notifying its failure to be 'ready'

15

startup.retriesOnFailureBeforeReady

How many quick retries we will do while starting up the SDK

0

startup.readyTimeout

Maximum amount of time to wait before notifying a timeout. Zero means no timeout, so no 'SDK_READY_TIMED_OUT' event will be fired by default.

0

storage.type

Storage type to be used by the SDK. Possible values are:
'MEMORY' | 'LOCALSTORAGE' | 'REDIS'

'MEMORY'

storage.options

Options to be passed to the storage instance. Only usable with REDIS type storage for now

{}
No default options

storage.prefix

An optional prefix for your data, to avoid collisions.

'SPLITIO'

mode

The SDK mode. Possible values are: 'standalone' | 'consumer'

standalone

debug

Boolean flag for activating SDK logs.

false

To set each of the parameters defined above, the syntax should be as follows:

var factory = SplitFactory({
  core: {
    authorizationKey: 'YOUR_API_KEY',
    labelsEnabled: true
  },
  scheduler: {
    featuresRefreshRate:    30, // 30 sec 
    segmentsRefreshRate:    60, // 60 sec 
    metricsRefreshRate:     60, // 60 sec
    impressionsRefreshRate: 60, // 60 sec
    eventsPushRate:         60, // 60 sec
    eventsQueueSize:       500  // 500 items
  },
  startup: {
    requestTimeoutBeforeReady: 1.5, // 1500 ms
    retriesOnFailureBeforeReady: 3, // 3 times
    readyTimeout: 5                 // 5 sec
  },
  storage: {
    type: 'REDIS',
    options: {},
    prefix: 'MYPREFIX'
  },
  mode: 'standalone',
  debug: false
});
const factory: SplitIO.ISDK = SplitFactory({
  core: {
    authorizationKey: 'YOUR_API_KEY',
    labelsEnabled: true
  },
  scheduler: {
    featuresRefreshRate:    30, // 30 sec 
    segmentsRefreshRate:    60, // 60 sec 
    metricsRefreshRate:     60, // 60 sec
    impressionsRefreshRate: 60, // 60 sec
    eventsPushRate:         60, // 60 sec
    eventsQueueSize:       500  // 500 items
  },
  storage: {
    type: 'REDIS',
    options: {},
    prefix: 'MYPREFIX'
  },
  mode: 'standalone',
  debug: false
});

Sharing state: Redis Integration

By default, the Split client stores the state it needs to compute treatments (rollout plans, segments etc.) in memory. As a result , it is very easy to get set up with Split: simply instantiate a client and start using it.

This simplicity hides one important detail that is worth exploring. Since each Split client downloads and stores state separately, a change in a Split is picked up by every client on its own schedule. Thus, if a customer issues back to back requests that are served by two different machines behind a load balancer, the customer can see different treatments for the same Split because one Split client may not have picked up the latest change. This drift in Split clients is natural and usually ignorable as long as each client sets an aggressive value for FeaturesRefreshRate and SegmentsRefreshRate. You can lear more about setting these rates in the Configuration section below.

However, if your application requires a total guarantee that Split clients across your entire infrastructure pick up a change in a Split at the exact same time or you need an async data store, then the only way to ensure that is to externalize the state of the Split client in a data store hosted on your infrastructure.

We currently support Redis for this external data store.

To use the Node.js SDK with Redis, you'll just need to setup the Split Synchronizer and instantiate the SDK in Consumer mode.

Split Synchronizer

Jump to our Split Synchronizer documents and follow the steps there to get everything set to sync data to your Redis Cache. Once you've done that, come back to set up the SDK in consumer mode!

Consumer Mode

In this mode, a client can be embedded in your application code and respond to calls to get_treatment by retrieving state from the data store (Redis in this case).

Here is how you can configure and get treatments a Split client in consumer mode:

var SplitFactory = require('@splitsoftware/splitio').SplitFactory;

var config = {
  mode: 'consumer', // changing the mode to consuemer here
  core: {
    authorizationKey: '<your-api-token>'
  },
  // defining the location of the Redis cache that the SDK should talk to
  storage: { 
    type: 'REDIS',
    options: {
      url: 'redis://<your-redis-server>:<your-redis-server-port>/0'
    },
    prefix: 'nodejs' // Optional prefix to prevent any kind of
    								 // data collision between SDK versions.
  }
};

var factory = SplitFactory(config);
var client = factory.client();

// Redis in NodeJS is async, this means we run the evaluation in a async way.
// You have as 2 different syntaxes to getTreatments: 

// one is the new async/await mechanism
var treatment = await client.getTreatment('my-feature-comming-from-localstorage');

// or just using the returned promise
client.getTreatment('my-feature-comming-from-localstorage')
  .then(treatment => {
  	// do something with the treatment
	});
import { SplitFactory } from '@splitsoftware/splitio';

const config: SplitIO.INodeAsyncSettings = {
  mode: 'consumer', // changing the mode to consumer here
  core: {
    authorizationKey: '<your-api-token>'
  },
  // defining the location of the Redis cache that the SDK should talk to
  storage: { 
    type: 'REDIS',
    options: {
      url: 'redis://<your-redis-server>:<your-redis-server-port>/0'
    },
    prefix: 'nodejs' // Optional prefix to prevent any kind of
                     // data collision between SDK versions.
  }
};

const factory: SplitIO.IAsyncSDK = SplitFactory(config);
const client: SplitIO.IAsyncClient = factory.client();

// Redis in NodeJS is async, this means we run the evaluation in a async way.
// You have 2 different syntaxes to interact with your
// getTreatments results now: 

// One, by just using the returned promise
client.getTreatment('my-feature-comming-from-localstorage')
  .then(treatment => {
  	// do something with the treatment
	});

// Or you can use the new async/await mechanism 
const treatment = await client.getTreatment('my-feature-comming-from-localstorage');
// do something with the treatment

// NOTE: async/await is supported for all targets since TypeScript 2.1. 
// See https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#downlevel-async-functions for details.

Localhost Mode

Features start their life on one developer's machine. A developer should be able to put code behind Splits on their development machine without the SDK requiring network connectivity. To achieve this, the Split SDK can be started in 'localhost' (aka off-the-grid mode). In this mode, the SDK neither polls nor updates Split servers, rather it uses an in-memory data structure to determine what treatments to show to the logged in customer for each of the features.

Here is how you can start the SDK in 'localhost' mode:

var factory = SplitFactory({
  core: {
    authorizationKey: 'localhost'
  },
  features: path.join(__dirname, '.split'),
  scheduler: {
    offlineRefreshRate: 15 // 15 sec
  }
});

var client = factory.client();

client.on(client.Event.SDK_READY, function() {
// The following code will be evaluated once the engine finalizes 
// the initialization
  var t1 = client.getTreatment('reporting_v2');
  var t2 = client.getTreatment('billing_updates');
  var t3 = client.getTreatment('navigation_bar_changes');
});
const factory: SplitIO.ISDK = SplitFactory({
  core: {
    authorizationKey: 'localhost'
  },
  features: path.join(__dirname, '.split'),
  scheduler: {
    offlineRefreshRate: 15 // 15 sec
  }
});

const client: SplitIO.IClient = factory.client();

client.on(client.Event.SDK_READY, function() {
// The following code will be evaluated once the engine finalizes 
// the initialization
  const t1: SplitIO.Treatment = client.getTreatment('reporting_v2');
  const t2: SplitIO.Treatment = client.getTreatment('billing_updates');
  const t3: SplitIO.Treatment = client.getTreatment('navigation_bar_changes');
});

In this mode, the SDK loads a mapping of Split name to treatment from a file at $HOME/.split. For a given Split, the treatment specified in the file will be returned for every customer.

All getTreatment calls for a Split will now only return the one treatment you've defined in the file. You can then change the treatment as necessary for your testing in the file. Any feature that is not provided in the 'features' map will return the Control Treatment if the SDK is asked to evaluate them.

The format of this file is two columns separated by a whitespace. The left column is the Split name, the right column is the treatment name. Here is a sample .split file:

# this is a comment

reporting_v2 on # sdk.getTreatment(*, reporting_v2) will return 'on'

double_writes_to_cassandra off

new-navigation v3

In addition, there are some extra configuration parameters that can be used when instantiating the SDK in localhost mode:

Configuration
Description
Default value

scheduler.offlineRefreshRate

The refresh interval for the mocked features treatments.

15

features

The path to the file with the mocked split data.

$HOME/.split

Manager

In order to obtain a list of features available in the in-memory dataset used by the Split client, use the Split Manager.

To instantiate a Manager in your code base, use the same factory that you used for your client:

var factory = SplitFactory({ 
  core: {
    authorizationKey: 'YOUR_API_KEY'
  }
});

var manager = factory.manager();
const factory: SplitIO.ISDK = SplitFactory({ 
  core: {
    authorizationKey: 'YOUR_API_KEY'
  }
});

const manager: SplitIO.IManager = factory.manager();

The Manager instance has the following methods available:

/**
 * Returns the Split registered with the SDK of this name.
 *
 * @return SplitView or null.
 */
var splitView = manager.split('name-of-split');

/**
 * Retrieves the Splits that are currently registered with the
 * SDK.
 *
 * returns a List of SplitViews.
 */
var splitViewsList = manager.splits();

/** 
 * Returns the names of features (or Splits) registered with the SDK.
 *
 * @return a List of Strings of the features' names.
 */
var splitNamesList = manager.names();
/**
 * Returns the Split registered with the SDK of this name.
 *
 * @return SplitView or null.
 */
const splitView: SplitIO.SplitView = manager.split('name-of-split');

/**
 * Retrieves the Splits that are currently registered with the
 * SDK.
 *
 * returns a List of SplitViews.
 */
const splitViewsList: SplitIO.SplitViews = manager.splits();

/** 
 * Returns the names of features (or Splits) registered with the SDK.
 *
 * @return a List of Strings of the features' names.
 */
const splitNamesList: SplitIO.SplitNames = manager.names();

A SplitView has the following structure:

class SplitView {
	name,
 	trafficType,
	killed,
	treatments,
	changeNumber
}

Listener

Split SDKs send impression data back to Split servers periodically and as a result of evaluating splits. In order to additionally send this information to a location of your choice, define and attach an impression listener. For that purpose, SDK's configurations have a parameter called impressionListener where an implementation of ImpressionListener could be added. This implementation must define the logImpression method and it will receive data in the following schema:

Name
Type
Description

impression

Object / SplitIO.Impression

Impression object that has the feature, key, treatment, label, etc.

attributes

Object / SplitIO.Attributes

A map of attributes passed to getTreatment/s (if any).

ip

String

Corresponds to the IP of the machine where the SDK is running.

hostname

String

The hostname of the OS where the SDK is running.

sdkLanguageVersion

String

Indicates the version of the sdk. In this case the language will be nodejs plus the version currently running.

Implementing custom Impression Listener

Below you could find an example of how implement a custom Impression Listener:

function logImpression(impressionData) {
  // do something with the impression data.
}

var factory = SplitFactory({ 
  core: {
    authorizationKey: 'YOUR_API_KEY'
  },
  impressionListener: {
    logImpression: logImpression
  }
});
class MyImprListener implements SplitIO.IImpressionListener {
  logImpression(impressionData: SplitIO.ImpressionData) {
    // do something with impressionData
  }
}

const factory: SplitIO.ISDK = SplitFactory({ 
  core: {
    authorizationKey: 'YOUR_API_KEY'
  },
  impressionListener: {
    logImpression: new MyImprListener()
  }
});

Impression listener is called asynchronously from the corresponding evaluation, but is almost immediate.
While the SDK will not fail if there's an exception in the listener, don't block the call stack.

Logging

To enable SDK logging in your Node.js app, set the SPLITIO_DEBUG environment variable as follows:

# Acceptable values are 'on', 'enable' and 'enabled'
SPLITIO_DEBUG='on' node app.js
DEBUG=splitio* node app.js

Since v9.2.0 of the SDK, you can also enable the logging via SDK settings and programmatically by calling the Logger API:

var SplitFactory = require('@splitsoftware/splitio').SplitFactory;

var factory = SplitFactory({
  core: {
    authorizationKey: 'YOUR_API_KEY'
  },
  debug: true // Debug boolean option can be passed on settings
});

// Or you can use the Logger API which two methods, enable and disable, will have an immediate effect.
factory.Logger.enable();
factory.Logger.disable();
import { SplitFactory } from '@splitsoftware/splitio';

const sdk: SplitIO.ISDK = SplitFactory({ 
  core: {
    authorizationKey: 'YOUR_API_KEY'
  },
  debug: true  // Debug boolean option can be passed on settings
});

// Or you can use the Logger API which two methods,
// enable and disable, which will have an immediate effect.
sdk.Logger.enable();
sdk.Logger.disable();

Example output:

Shutdown

client.destroy() gracefully shuts down the Split SDK by stopping all background tasks, clearing caches, removing references and flushing the remaining unpublished impressions.

user_client.destroy();
user_client = null;

After destroy() has been called and finished, any subsequent invocations to getTreatment/s or manager methods, will result in control or empty list respectively.

Please note that when calling destroy() method the factory object is also destroyed, in order to create new client instance, a new factory instance should be created first.

Advanced: Working with both Sync and Async storages

You may have an application that you want to run on REDIS and MEMORY storage types for example, but that can sound tricky at first look.

A recommended way to write a code that works with all type of SDK storages is to check if the treatments are thenable objects to decide when to execute the code that depends on the split.

See example below:

var treatment = client.getTreatment("CUSTOMER_ID", "SPLIT_NAME");

if (thenable(treatment)) {
  // We have a promise so we will use the treatment in the cb, which will receive the treatment string.
  asyncTreatment.then(useTreatment);
} else {
  // We have the actual string.
  useTreatment(treatment);
}

function useTreatment(splitTreatment) {
  if (splitTreatment == "on") {
    // insert code here to show on treatment
  } else if (splitTreatment == "off") {
    // insert code here to show off treatment
  } else {
    // insert your control treatment code here
  }
}

function thenable(val) {
  // By definition, “thenable” is an object or function that defines a then method.
  return val !== undefined && typeof val.then === 'function';
}
const treatment: (SplitIO.Treatment | SplitIO.AsyncTreatment) =
  client.getTreatment("CUSTOMER_ID", "SPLIT_NAME");

if (thenable(treatment)) {
  // We have a promise so we will use the treatment in the cb,
  // which will receive the treatment string.
  treatment.then(useTreatment);
} else {
  // We have the actual string.
  useTreatment(treatment);
}

function useTreatment(splitTreatment) {
  if (splitTreatment == 'on') {
    // insert code here to show on treatment
  } else if (splitTreatment == 'off') {
    // insert code here to show off treatment
  } else {
    // insert your control treatment code here
  }
}

function thenable(val) {
  // By definition, “thenable” is an object or function that defines a then method.
  return val !== undefined && typeof val.then === 'function';
}