Introduce Batchinator

Reviewed By: devknoll

Differential Revision: D3796349

fbshipit-source-id: 2e23a2361a612107596cf6381e67252238e970bf
This commit is contained in:
Spencer Ahrens 2016-09-06 14:36:30 -07:00 committed by Facebook Github Bot
parent 2554f26387
commit 5eaef1c631
2 changed files with 160 additions and 0 deletions

View File

@ -0,0 +1,77 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule Batchinator
* @flow
*/
'use strict';
const InteractionManager = require('InteractionManager');
/**
* A simple class for batching up invocations of a low-pri callback. A timeout is set to run the
* callback once after a delay, no matter how many times it's scheduled. Once the delay is reached,
* InteractionManager.runAfterInteractions is used to invoke the callback after any hi-pri
* interactions are done running.
*
* Make sure to cleanup with dispose(). Example:
*
* class Widget extends React.Component {
* _batchedSave: new Batchinator(() => this._saveState, 1000);
* _saveSate() {
* // save this.state to disk
* }
* componentDidUpdate() {
* this._batchedSave.schedule();
* }
* componentWillUnmount() {
* this._batchedSave.dispose();
* }
* ...
* }
*/
class Batchinator {
_callback: () => void;
_delay: number;
_taskHandle: ?{cancel: () => void};
constructor(callback: () => void, delayMS: number) {
this._delay = delayMS;
this._callback = callback;
}
/*
* Cleanup any pending tasks.
*
* By default, if there is a pending task the callback is run immediately. Set the option abort to
* true to not call the callback if it was pending.
*/
dispose(options: {abort: boolean} = {abort: false}) {
if (this._taskHandle) {
this._taskHandle.cancel();
if (!options.abort) {
this._callback();
}
this._taskHandle = null;
}
}
schedule() {
if (this._taskHandle) {
return;
}
const timeoutHandle = setTimeout(() => {
this._taskHandle = InteractionManager.runAfterInteractions(() => {
// Note that we clear the handle before invoking the callback so that if the callback calls
// schedule again, it will actually schedule another task.
this._taskHandle = null;
this._callback();
});
}, this._delay);
this._taskHandle = {cancel: () => clearTimeout(timeoutHandle)};
}
}
module.exports = Batchinator;

View File

@ -0,0 +1,83 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
'use strict';
jest
.disableAutomock()
.mock('ErrorUtils')
.mock('BatchedBridge');
function expectToBeCalledOnce(fn) {
expect(fn.mock.calls.length).toBe(1);
}
describe('Batchinator', () => {
const Batchinator = require('Batchinator');
it('executes vanilla tasks', () => {
const callback = jest.fn();
const batcher = new Batchinator(callback, 10000);
batcher.schedule();
jest.runAllTimers();
expectToBeCalledOnce(callback);
});
it('batches up tasks', () => {
const callback = jest.fn();
const batcher = new Batchinator(callback, 10000);
batcher.schedule();
batcher.schedule();
batcher.schedule();
batcher.schedule();
expect(callback).not.toBeCalled();
jest.runAllTimers();
expectToBeCalledOnce(callback);
});
it('flushes on dispose', () => {
const callback = jest.fn();
const batcher = new Batchinator(callback, 10000);
batcher.schedule();
batcher.schedule();
batcher.dispose();
expectToBeCalledOnce(callback);
jest.runAllTimers();
expectToBeCalledOnce(callback);
});
it('should call tasks scheduled by the callback', () => {
let batcher = null;
let hasRescheduled = false;
const callback = jest.fn(() => {
if (!hasRescheduled) {
batcher.schedule();
hasRescheduled = true;
}
});
batcher = new Batchinator(callback, 10000);
batcher.schedule();
jest.runAllTimers();
expect(callback.mock.calls.length).toBe(2);
});
it('does not run callbacks more than once', () => {
const callback = jest.fn();
const batcher = new Batchinator(callback, 10000);
batcher.schedule();
batcher.schedule();
jest.runAllTimers();
expectToBeCalledOnce(callback);
jest.runAllTimers();
expectToBeCalledOnce(callback);
batcher.dispose();
expectToBeCalledOnce(callback);
});
});