Bug 1364150 - Introduce debounce middleware; r=bgrins

MozReview-Commit-ID: Hu3rQ3iJHzP

--HG--
extra : rebase_source : 1f3d8b079a930eca7e4e807d6833e60258ef2bcf
This commit is contained in:
Brian Grinstead 2017-05-11 12:40:20 -07:00
Родитель 6fd9167b8e
Коммит 1b86a2bfab
9 изменённых файлов: 166 добавлений и 26 удалений

Просмотреть файл

@ -0,0 +1,96 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/**
* Redux middleware for debouncing actions.
*
* Schedules actions with { meta: { debounce: true } } to be delayed
* by wait milliseconds. If another action is fired during this
* time-frame both actions are inserted into a queue and delayed.
* Maximum delay is defined by maxWait argument.
*
* Handling more actions at once results in better performance since
* components need to be re-rendered less often.
*
* @param string wait Wait for specified amount of milliseconds
* before executing an action. The time is used
* to collect more actions and handle them all
* at once.
* @param string maxWait Max waiting time. It's used in case of
* a long stream of actions.
*/
function debounceActions(wait, maxWait) {
let queuedActions = [];
return store => next => {
let debounced = debounce(() => {
next(batchActions(queuedActions));
queuedActions = [];
}, wait, maxWait);
return action => {
if (!action.meta || !action.meta.debounce) {
return next(action);
}
if (!wait || !maxWait) {
return next(action);
}
if (action.type == BATCH_ACTIONS) {
queuedActions.push(...action.actions);
} else {
queuedActions.push(action);
}
return debounced();
};
};
}
function debounce(cb, wait, maxWait) {
let timeout, maxTimeout;
let doFunction = () => {
clearTimeout(timeout);
clearTimeout(maxTimeout);
timeout = maxTimeout = null;
cb();
};
return () => {
return new Promise(resolve => {
let onTimeout = () => {
doFunction();
resolve();
};
clearTimeout(timeout);
timeout = setTimeout(onTimeout, wait);
if (!maxTimeout) {
maxTimeout = setTimeout(onTimeout, maxWait);
}
});
};
}
const BATCH_ACTIONS = Symbol("BATCH_ACTIONS");
/**
* Action creator for action-batching.
*/
function batchActions(batchedActions, debounceFlag = true) {
return {
type: BATCH_ACTIONS,
meta: { debounce: debounceFlag },
actions: batchedActions,
};
}
module.exports = {
BATCH_ACTIONS,
batchActions,
debounceActions,
};

Просмотреть файл

@ -5,6 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'debounce.js',
'history.js',
'log.js',
'promise.js',

Просмотреть файл

@ -1,20 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { BATCH_ACTIONS } = require("../constants");
function batchActions(batchedActions) {
return {
type: BATCH_ACTIONS,
actions: batchedActions,
};
}
module.exports = {
batchActions
};

Просмотреть файл

@ -7,7 +7,6 @@
"use strict";
const actionModules = [
require("./enhancers"),
require("./filters"),
require("./messages"),
require("./ui"),

Просмотреть файл

@ -10,7 +10,7 @@ const {
prepareMessage
} = require("devtools/client/webconsole/new-console-output/utils/messages");
const { IdGenerator } = require("devtools/client/webconsole/new-console-output/utils/id-generator");
const { batchActions } = require("devtools/client/webconsole/new-console-output/actions/enhancers");
const { batchActions } = require("devtools/client/shared/redux/middleware/debounce");
const {
MESSAGE_ADD,
NETWORK_MESSAGE_UPDATE,

Просмотреть файл

@ -4,7 +4,6 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'enhancers.js',
'filters.js',
'index.js',
'messages.js',

Просмотреть файл

@ -9,6 +9,7 @@ const ReactDOM = require("devtools/client/shared/vendor/react-dom");
const { Provider } = require("devtools/client/shared/vendor/react-redux");
const actions = require("devtools/client/webconsole/new-console-output/actions/index");
const { batchActions } = require("devtools/client/shared/redux/middleware/debounce");
const { createContextMenu } = require("devtools/client/webconsole/new-console-output/utils/context-menu");
const { configureStore } = require("devtools/client/webconsole/new-console-output/store");
@ -172,7 +173,7 @@ NewConsoleOutputWrapper.prototype = {
dispatchMessagesAdd: function (messages) {
const batchedActions = messages.map(message => actions.messageAdd(message));
store.dispatch(actions.batchActions(batchedActions));
store.dispatch(batchActions(batchedActions));
},
dispatchMessagesClear: function () {
@ -204,7 +205,7 @@ function batchedMessageAdd(action) {
queuedActions.push(action);
if (!throttledDispatchTimeout) {
throttledDispatchTimeout = setTimeout(() => {
store.dispatch(actions.batchActions(queuedActions));
store.dispatch(batchActions(queuedActions));
queuedActions = [];
throttledDispatchTimeout = null;
}, 50);

Просмотреть файл

@ -12,11 +12,13 @@ const {
createStore
} = require("devtools/client/shared/vendor/redux");
const { thunk } = require("devtools/client/shared/redux/middleware/thunk");
const {
BATCH_ACTIONS
} = require("devtools/client/shared/redux/middleware/debounce");
const {
MESSAGE_ADD,
MESSAGES_CLEAR,
REMOVED_MESSAGES_CLEAR,
BATCH_ACTIONS,
PREFS,
} = require("devtools/client/webconsole/new-console-output/constants");
const { reducers } = require("./reducers/index");

Просмотреть файл

@ -0,0 +1,62 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const expect = require("expect");
const {
debounceActions,
} = require("devtools/client/shared/redux/middleware/debounce");
describe("Debounce Middleware", () => {
let nextArgs = [];
const fakeStore = {};
const fakeNext = (...args) => {
nextArgs.push(args);
};
beforeEach(() => {
nextArgs = [];
});
it("should pass the intercepted action to next", () => {
const fakeAction = {
type: "FAKE_ACTION"
};
debounceActions()(fakeStore)(fakeNext)(fakeAction);
expect(nextArgs.length).toEqual(1);
expect(nextArgs[0]).toEqual([fakeAction]);
});
it("should debounce if specified", () => {
const fakeAction = {
type: "FAKE_ACTION",
meta: {
debounce: true
}
};
const executed = debounceActions(1, 1)(fakeStore)(fakeNext)(fakeAction);
expect(nextArgs.length).toEqual(0);
return executed.then(() => {
expect(nextArgs.length).toEqual(1);
});
});
it("should have no effect if no timeout", () => {
const fakeAction = {
type: "FAKE_ACTION",
meta: {
debounce: true
}
};
debounceActions()(fakeStore)(fakeNext)(fakeAction);
expect(nextArgs.length).toEqual(1);
expect(nextArgs[0]).toEqual([fakeAction]);
});
});