Skip to content

Commit b8719b2

Browse files
Polishing
1 parent 36ca35e commit b8719b2

File tree

3 files changed

+55
-23
lines changed

3 files changed

+55
-23
lines changed

CHANGES.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
2.8.0 (October XX, 2025)
2-
- Added `client.whenReady()` and `client.whenReadyFromCache()` methods to replace the deprecated `client.ready()` method, which had an issue causing the returned promise to hang when using async/await syntax if it was rejected.
2+
- Added `client.whenReady()` and `client.whenReadyFromCache()` methods to replace the deprecated `client.ready()` method, which has an issue causing the returned promise to hang when using async/await syntax if it was rejected.
33
- Updated the SDK_READY_FROM_CACHE event to be emitted alongside the SDK_READY event if it hasn’t already been emitted.
44

55
2.7.1 (October 8, 2025)

src/readiness/__tests__/sdkReadinessManager.spec.ts

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
// @ts-nocheck
22
import { loggerMock } from '../../logger/__tests__/sdkLogger.mock';
33
import SplitIO from '../../../types/splitio';
4-
import { SDK_READY, SDK_READY_FROM_CACHE, SDK_READY_TIMED_OUT, SDK_UPDATE } from '../constants';
4+
import { SDK_READY, SDK_READY_FROM_CACHE, SDK_READY_TIMED_OUT, SDK_UPDATE, SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../constants';
55
import { sdkReadinessManagerFactory } from '../sdkReadinessManager';
66
import { IReadinessManager } from '../types';
77
import { ERROR_CLIENT_LISTENER, CLIENT_READY_FROM_CACHE, CLIENT_READY, CLIENT_NO_LISTENER } from '../../logger/constants';
88
import { fullSettings } from '../../utils/settingsValidation/__tests__/settings.mocks';
9+
import { EventEmitter } from '../../utils/MinEvents';
910

1011
const EventEmitterMock = jest.fn(() => ({
1112
on: jest.fn(),
@@ -24,6 +25,7 @@ function emitReadyEvent(readinessManager: IReadinessManager) {
2425
readinessManager.segments.once.mock.calls[0][1]();
2526
readinessManager.segments.on.mock.calls[0][1]();
2627
readinessManager.gate.once.mock.calls[0][1]();
28+
if (readinessManager.gate.once.mock.calls[3]) readinessManager.gate.once.mock.calls[3][1](); // whenReady promise
2729
}
2830

2931
const timeoutErrorMessage = 'Split SDK emitted SDK_READY_TIMED_OUT event.';
@@ -32,6 +34,7 @@ const timeoutErrorMessage = 'Split SDK emitted SDK_READY_TIMED_OUT event.';
3234
function emitTimeoutEvent(readinessManager: IReadinessManager) {
3335
readinessManager.gate.once.mock.calls[1][1](timeoutErrorMessage);
3436
readinessManager.hasTimedout = () => true;
37+
if (readinessManager.gate.once.mock.calls[4]) readinessManager.gate.once.mock.calls[4][1](timeoutErrorMessage); // whenReady promise
3538
}
3639

3740
describe('SDK Readiness Manager - Event emitter', () => {
@@ -50,7 +53,8 @@ describe('SDK Readiness Manager - Event emitter', () => {
5053
expect(sdkStatus[propName]).toBeTruthy(); // The sdkStatus exposes all minimal EventEmitter functionality.
5154
});
5255

53-
expect(typeof sdkStatus.ready).toBe('function'); // The sdkStatus exposes a .ready() function.
56+
expect(typeof sdkStatus.whenReady).toBe('function'); // The sdkStatus exposes a .whenReady() function.
57+
expect(typeof sdkStatus.whenReadyFromCache).toBe('function'); // The sdkStatus exposes a .whenReadyFromCache() function.
5458
expect(typeof sdkStatus.__getStatus).toBe('function'); // The sdkStatus exposes a .__getStatus() function.
5559
expect(sdkStatus.__getStatus()).toEqual({
5660
isReady: false, isReadyFromCache: false, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: false, lastUpdate: 0
@@ -67,9 +71,9 @@ describe('SDK Readiness Manager - Event emitter', () => {
6771
const sdkReadyResolvePromiseCall = gateMock.once.mock.calls[0];
6872
const sdkReadyRejectPromiseCall = gateMock.once.mock.calls[1];
6973
const sdkReadyFromCacheListenersCheckCall = gateMock.once.mock.calls[2];
70-
expect(sdkReadyResolvePromiseCall[0]).toBe(SDK_READY); // A one time only subscription is on the SDK_READY event, for resolving the full blown ready promise and to check for callbacks warning.
71-
expect(sdkReadyRejectPromiseCall[0]).toBe(SDK_READY_TIMED_OUT); // A one time only subscription is also on the SDK_READY_TIMED_OUT event, for rejecting the full blown ready promise.
72-
expect(sdkReadyFromCacheListenersCheckCall[0]).toBe(SDK_READY_FROM_CACHE); // A one time only subscription is on the SDK_READY_FROM_CACHE event, to log the event and update internal state.
74+
expect(sdkReadyResolvePromiseCall[0]).toBe(SDK_READY); // A one time only subscription is on the SDK_READY event
75+
expect(sdkReadyRejectPromiseCall[0]).toBe(SDK_READY_TIMED_OUT); // A one time only subscription is also on the SDK_READY_TIMED_OUT event
76+
expect(sdkReadyFromCacheListenersCheckCall[0]).toBe(SDK_READY_FROM_CACHE); // A one time only subscription is on the SDK_READY_FROM_CACHE event
7377

7478
expect(gateMock.on).toBeCalledTimes(2); // It should also add two persistent listeners
7579

@@ -98,7 +102,7 @@ describe('SDK Readiness Manager - Event emitter', () => {
98102

99103
emitReadyEvent(sdkReadinessManager.readinessManager);
100104

101-
expect(loggerMock.warn).toBeCalledTimes(1); // If the SDK_READY event fires and we have no callbacks for it (neither event nor ready promise) we get a warning.
105+
expect(loggerMock.warn).toBeCalledTimes(1); // If the SDK_READY event fires and we have no callbacks for it (neither event nor whenReady promise) we get a warning.
102106
expect(loggerMock.warn).toBeCalledWith(CLIENT_NO_LISTENER); // Telling us there were no listeners and evaluations before this point may have been incorrect.
103107

104108
expect(loggerMock.info).toBeCalledTimes(1); // If the SDK_READY event fires, we get a info message.
@@ -199,15 +203,15 @@ describe('SDK Readiness Manager - Event emitter', () => {
199203
});
200204
});
201205

202-
describe('SDK Readiness Manager - Ready promise', () => {
206+
describe('SDK Readiness Manager - whenReady promise', () => {
203207

204-
test('.ready() promise behavior for clients', async () => {
208+
test('.whenReady() promise behavior for clients', async () => {
205209
const sdkReadinessManager = sdkReadinessManagerFactory(EventEmitterMock, fullSettings);
206210

207-
const ready = sdkReadinessManager.sdkStatus.ready();
211+
const ready = sdkReadinessManager.sdkStatus.whenReady();
208212
expect(ready instanceof Promise).toBe(true); // It should return a promise.
209213

210-
// make the SDK "ready"
214+
// make the SDK ready
211215
emitReadyEvent(sdkReadinessManager.readinessManager);
212216

213217
let testPassedCount = 0;
@@ -219,8 +223,8 @@ describe('SDK Readiness Manager - Ready promise', () => {
219223
() => { throw new Error('It should be resolved on ready event, not rejected.'); }
220224
);
221225

222-
// any subsequent call to .ready() must be a resolved promise
223-
await ready.then(
226+
// any subsequent call to .whenReady() must be a resolved promise
227+
await sdkReadinessManager.sdkStatus.whenReady().then(
224228
() => {
225229
expect('A subsequent call should be a resolved promise.');
226230
testPassedCount++;
@@ -233,9 +237,9 @@ describe('SDK Readiness Manager - Ready promise', () => {
233237

234238
const sdkReadinessManagerForTimedout = sdkReadinessManagerFactory(EventEmitterMock, fullSettings);
235239

236-
const readyForTimeout = sdkReadinessManagerForTimedout.sdkStatus.ready();
240+
const readyForTimeout = sdkReadinessManagerForTimedout.sdkStatus.whenReady();
237241

238-
emitTimeoutEvent(sdkReadinessManagerForTimedout.readinessManager); // make the SDK "timed out"
242+
emitTimeoutEvent(sdkReadinessManagerForTimedout.readinessManager); // make the SDK timeout
239243

240244
await readyForTimeout.then(
241245
() => { throw new Error('It should be a promise that was rejected on SDK_READY_TIMED_OUT, not resolved.'); },
@@ -245,20 +249,20 @@ describe('SDK Readiness Manager - Ready promise', () => {
245249
}
246250
);
247251

248-
// any subsequent call to .ready() must be a rejected promise
249-
await readyForTimeout.then(
252+
// any subsequent call to .whenReady() must be a rejected promise until the SDK is ready
253+
await sdkReadinessManagerForTimedout.sdkStatus.whenReady().then(
250254
() => { throw new Error('It should be a promise that was rejected on SDK_READY_TIMED_OUT, not resolved.'); },
251255
() => {
252256
expect('A subsequent call should be a rejected promise.');
253257
testPassedCount++;
254258
}
255259
);
256260

257-
// make the SDK "ready"
261+
// make the SDK ready
258262
emitReadyEvent(sdkReadinessManagerForTimedout.readinessManager);
259263

260-
// once SDK_READY, `.ready()` returns a resolved promise
261-
await ready.then(
264+
// once SDK_READY, `.whenReady()` returns a resolved promise
265+
await sdkReadinessManagerForTimedout.sdkStatus.whenReady().then(
262266
() => {
263267
expect('It should be a resolved promise when the SDK is ready, even after an SDK timeout.');
264268
loggerMock.mockClear();
@@ -269,7 +273,35 @@ describe('SDK Readiness Manager - Ready promise', () => {
269273
);
270274
});
271275

272-
test('Full blown ready promise count as a callback and resolves on SDK_READY', (done) => {
276+
test('whenReady promise count as a callback and resolves on SDK_READY', (done) => {
277+
let sdkReadinessManager = sdkReadinessManagerFactory(EventEmitter, fullSettings);
278+
279+
// Emit ready event
280+
sdkReadinessManager.readinessManager.splits.emit(SDK_SPLITS_ARRIVED);
281+
sdkReadinessManager.readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED);
282+
283+
expect(loggerMock.warn).toBeCalledWith(CLIENT_NO_LISTENER); // We should get a warning if the SDK get's ready before calling the whenReady method or attaching a listener to the ready event
284+
loggerMock.warn.mockClear();
285+
286+
sdkReadinessManager = sdkReadinessManagerFactory(EventEmitter, fullSettings);
287+
sdkReadinessManager.sdkStatus.whenReady().then(() => {
288+
expect('whenReady promise is resolved when the gate emits SDK_READY.');
289+
done();
290+
}, () => {
291+
throw new Error('This should not be called as the promise is being resolved.');
292+
});
293+
294+
// Emit ready event
295+
sdkReadinessManager.readinessManager.splits.emit(SDK_SPLITS_ARRIVED);
296+
sdkReadinessManager.readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED);
297+
298+
expect(loggerMock.warn).not.toBeCalled(); // But if we have a listener or call the whenReady method, we get no warnings.
299+
});
300+
});
301+
302+
// @TODO: remove in next major
303+
describe('SDK Readiness Manager - Ready promise', () => {
304+
test('ready promise count as a callback and resolves on SDK_READY', (done) => {
273305
const sdkReadinessManager = sdkReadinessManagerFactory(EventEmitterMock, fullSettings);
274306
const readyPromise = sdkReadinessManager.sdkStatus.ready();
275307

src/readiness/sdkReadinessManager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { ERROR_CLIENT_LISTENER, CLIENT_READY_FROM_CACHE, CLIENT_READY, CLIENT_NO
99

1010
const NEW_LISTENER_EVENT = 'newListener';
1111
const REMOVE_LISTENER_EVENT = 'removeListener';
12-
const TIMEOUT_ERROR = new Error('Split SDK has emitted SDK_READY_TIMED_OUT event.');
12+
const TIMEOUT_ERROR = new Error(SDK_READY_TIMED_OUT);
1313

1414
/**
1515
* SdkReadinessManager factory, which provides the public status API of SDK clients and manager: ready promise, readiness event emitter and constants (SDK_READY, etc).
@@ -98,7 +98,7 @@ export function sdkReadinessManagerFactory(
9898
ready() {
9999
if (readinessManager.hasTimedout()) {
100100
if (!readinessManager.isReady()) {
101-
return promiseWrapper(Promise.reject(TIMEOUT_ERROR), defaultOnRejected);
101+
return promiseWrapper(Promise.reject(new Error('Split SDK has emitted SDK_READY_TIMED_OUT event.')), defaultOnRejected);
102102
} else {
103103
return Promise.resolve();
104104
}

0 commit comments

Comments
 (0)