Skip to content

Commit fbe766e

Browse files
authored
feat(event processor)[OASIS-4921] Return Promise from close method representing the close process (#295)
Summary: The close method flushes the event processor's queue. If the queue was non-empty, requests will be sent to the event dispatcher. Users may want to wait until the dispatcher finishes sending these, so we return a Promise that fulfills after the event dispatcher calls the "done" callback for each request. The event processor already returns a Promise from its stop method, so this change is just making use of that. Test plan: New unit tests. Manually tested
1 parent 8f4f403 commit fbe766e

File tree

3 files changed

+137
-5
lines changed

3 files changed

+137
-5
lines changed

packages/optimizely-sdk/CHANGELOG.MD

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
77
## [Unreleased]
88
Changes that have landed but are not yet released.
99

10+
### New Features
11+
- Updated the `close` method to return a `Promise` representing the process of closing the instance. When `close` is called, any events waiting to be sent as part of a batched event request will be immediately batched and sent to the event dispatcher.
12+
- If any such requests were sent to the event dispatcher, `close` returns a `Promise` that fulfills after the event dispatcher calls the response callback for each request. Otherwise, `close` returns an immediately-fulfilled `Promise`.
13+
- The `Promise` returned from `close` is fulfilled with a result object containing `success` (boolean) and `reason` (string, only when success is `false`) properties. In the result object, `success` is `true` if all events in the queue at the time close was called were combined into requests, sent to the event dispatcher, and the event dispatcher called the callbacks for each request. `success` is false if an unexpected error was encountered during the close process.
14+
1015
## [3.2.1] - July 1st, 2019
1116

1217
### Changed

packages/optimizely-sdk/lib/optimizely/index.js

+49-5
Original file line numberDiff line numberDiff line change
@@ -850,11 +850,38 @@ Optimizely.prototype.getFeatureVariableString = function(featureKey, variableKey
850850
};
851851

852852
/**
853-
* Cleanup method for killing an running timers and flushing eventQueue
853+
* Stop background processes belonging to this instance, including:
854+
*
855+
* - Active datafile requests
856+
* - Pending datafile requests
857+
* - Pending event queue flushes
858+
*
859+
* In-flight datafile requests will be aborted. Any events waiting to be sent
860+
* as part of a batched event request will be immediately batched and sent to
861+
* the event dispatcher.
862+
*
863+
* If any such requests were sent to the event dispatcher, returns a Promise
864+
* that fulfills after the event dispatcher calls the response callback for each
865+
* request. Otherwise, returns an immediately-fulfilled Promise.
866+
*
867+
* Returned Promises are fulfilled with result objects containing these
868+
* properties:
869+
* - success (boolean): true if all events in the queue at the time close was
870+
* called were combined into requests, sent to the
871+
* event dispatcher, and the event dispatcher called the
872+
* callbacks for each request. false if an unexpected
873+
* error was encountered during the close process.
874+
* - reason (string=): If success is false, this is a string property with
875+
* an explanatory message.
876+
*
877+
* NOTE: After close is called, this instance is no longer usable - any events
878+
* generated will no longer be sent to the event dispatcher.
879+
*
880+
* @return {Promise}
854881
*/
855882
Optimizely.prototype.close = function() {
856883
try {
857-
this.eventProcessor.stop();
884+
var eventProcessorStoppedPromise = this.eventProcessor.stop();
858885
if (this.__disposeOnUpdate) {
859886
this.__disposeOnUpdate();
860887
this.__disposeOnUpdate = null;
@@ -868,9 +895,26 @@ Optimizely.prototype.close = function() {
868895
readyTimeoutRecord.onClose();
869896
}.bind(this));
870897
this.__readyTimeouts = {};
871-
} catch (e) {
872-
this.logger.log(LOG_LEVEL.ERROR, e.message);
873-
this.errorHandler.handleError(e);
898+
return eventProcessorStoppedPromise.then(
899+
function() {
900+
return {
901+
success: true,
902+
};
903+
},
904+
function(err) {
905+
return {
906+
success: false,
907+
reason: String(err),
908+
};
909+
}
910+
);
911+
} catch (err) {
912+
this.logger.log(LOG_LEVEL.ERROR, err.message);
913+
this.errorHandler.handleError(err);
914+
return Promise.resolve({
915+
success: false,
916+
reason: String(err),
917+
});
874918
}
875919
};
876920

packages/optimizely-sdk/lib/optimizely/index.tests.js

+83
Original file line numberDiff line numberDiff line change
@@ -4768,6 +4768,89 @@ describe('lib/optimizely', function() {
47684768
assert.deepEqual(eventDispatcherCall[0], expectedObj);
47694769
});
47704770
});
4771+
4772+
describe('close method', function() {
4773+
var eventProcessorStopPromise;
4774+
var optlyInstance;
4775+
var mockEventProcessor;
4776+
beforeEach(function() {
4777+
mockEventProcessor = {
4778+
process: sinon.stub(),
4779+
start: sinon.stub(),
4780+
stop: sinon.stub(),
4781+
};
4782+
sinon.stub(eventProcessor, 'LogTierV1EventProcessor').returns(mockEventProcessor);
4783+
});
4784+
4785+
afterEach(function() {
4786+
eventProcessor.LogTierV1EventProcessor.restore();
4787+
});
4788+
4789+
describe('when the event processor stop method returns a promise that fulfills', function() {
4790+
beforeEach(function() {
4791+
eventProcessorStopPromise = Promise.resolve();
4792+
mockEventProcessor.stop.returns(eventProcessorStopPromise);
4793+
optlyInstance = new Optimizely({
4794+
clientEngine: 'node-sdk',
4795+
datafile: testData.getTestProjectConfig(),
4796+
eventBuilder: eventBuilder,
4797+
errorHandler: errorHandler,
4798+
eventDispatcher: eventDispatcher,
4799+
jsonSchemaValidator: jsonSchemaValidator,
4800+
logger: createdLogger,
4801+
isValidInstance: true,
4802+
eventBatchSize: 3,
4803+
eventFlushInterval: 100,
4804+
});
4805+
});
4806+
4807+
afterEach(function() {
4808+
return eventProcessorStopPromise.catch(function() {
4809+
// Handle rejected promise - don't want test to fail
4810+
});
4811+
});
4812+
4813+
it('returns a promise that fulfills with a successful result object', function() {
4814+
return optlyInstance.close().then(function(result) {
4815+
assert.deepEqual(result, { success: true });
4816+
});
4817+
});
4818+
});
4819+
4820+
describe('when the event processor stop method returns a promise that rejects', function() {
4821+
beforeEach(function() {
4822+
eventProcessorStopPromise = Promise.reject(new Error('Failed to stop'));
4823+
mockEventProcessor.stop.returns(eventProcessorStopPromise);
4824+
optlyInstance = new Optimizely({
4825+
clientEngine: 'node-sdk',
4826+
datafile: testData.getTestProjectConfig(),
4827+
eventBuilder: eventBuilder,
4828+
errorHandler: errorHandler,
4829+
eventDispatcher: eventDispatcher,
4830+
jsonSchemaValidator: jsonSchemaValidator,
4831+
logger: createdLogger,
4832+
isValidInstance: true,
4833+
eventBatchSize: 3,
4834+
eventFlushInterval: 100,
4835+
});
4836+
});
4837+
4838+
afterEach(function() {
4839+
return eventProcessorStopPromise.catch(function() {
4840+
// Handle rejected promise - don't want test to fail
4841+
});
4842+
});
4843+
4844+
it('returns a promise that fulfills with an unsuccessful result object', function() {
4845+
return optlyInstance.close().then(function(result) {
4846+
assert.deepEqual(result, {
4847+
success: false,
4848+
reason: 'Error: Failed to stop',
4849+
});
4850+
});
4851+
});
4852+
});
4853+
});
47714854
});
47724855

47734856
describe('event processor defaults', function() {

0 commit comments

Comments
 (0)