batching.js (3243B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const { 8 BATCH_ACTIONS, 9 BATCH_ENABLE, 10 BATCH_RESET, 11 BATCH_FLUSH, 12 } = require("resource://devtools/client/netmonitor/src/constants.js"); 13 14 const REQUESTS_REFRESH_RATE = 50; // ms 15 16 /** 17 * Middleware that watches for actions with a "batch = true" value in their meta field. 18 * These actions are queued and dispatched as one batch after a timeout. 19 * Special actions that are handled by this middleware: 20 * - BATCH_ENABLE can be used to enable and disable the batching. 21 * - BATCH_RESET discards the actions that are currently in the queue. 22 */ 23 function batchingMiddleware() { 24 return next => { 25 let queuedActions = []; 26 let enabled = true; 27 let flushTask = null; 28 29 return action => { 30 if (action.type === BATCH_ENABLE) { 31 return setEnabled(action.enabled); 32 } 33 34 if (action.type === BATCH_RESET) { 35 return resetQueue(); 36 } 37 38 if (action.type === BATCH_FLUSH) { 39 return flushQueue(); 40 } 41 42 if (action.meta?.batch) { 43 if (!enabled) { 44 next(action); 45 return Promise.resolve(); 46 } 47 48 queuedActions.push(action); 49 50 if (!flushTask) { 51 flushTask = new DelayedTask(flushActions, REQUESTS_REFRESH_RATE); 52 } 53 54 return flushTask.promise; 55 } 56 57 return next(action); 58 }; 59 60 function setEnabled(value) { 61 enabled = value; 62 63 // If disabling the batching, flush the actions that have been queued so far 64 if (!enabled && flushTask) { 65 flushTask.runNow(); 66 } 67 } 68 69 function resetQueue() { 70 queuedActions = []; 71 72 if (flushTask) { 73 flushTask.cancel(); 74 flushTask = null; 75 } 76 } 77 78 function flushQueue() { 79 if (flushTask) { 80 flushTask.runNow(); 81 } 82 } 83 84 function flushActions() { 85 const actions = queuedActions; 86 queuedActions = []; 87 88 next({ 89 type: BATCH_ACTIONS, 90 actions, 91 }); 92 93 flushTask = null; 94 } 95 }; 96 } 97 98 /** 99 * Create a delayed task that calls the specified task function after a delay. 100 */ 101 class DelayedTask { 102 #promise; 103 constructor(taskFn, delay) { 104 this.#promise = new Promise((resolve, reject) => { 105 this.runTask = cancel => { 106 if (cancel) { 107 reject("Task cancelled"); 108 } else { 109 taskFn(); 110 resolve(); 111 } 112 this.runTask = null; 113 }; 114 this.timeout = setTimeout(this.runTask, delay); 115 }).catch(console.error); 116 } 117 /** 118 * Return a promise that is resolved after the task is performed or canceled. 119 */ 120 get promise() { 121 return this.#promise; 122 } 123 124 /** 125 * Cancel the execution of the task. 126 */ 127 cancel() { 128 clearTimeout(this.timeout); 129 if (this.runTask) { 130 this.runTask(true); 131 } 132 } 133 134 /** 135 * Execute the scheduled task immediately, without waiting for the timeout. 136 * Resolves the promise correctly. 137 */ 138 runNow() { 139 clearTimeout(this.timeout); 140 if (this.runTask) { 141 this.runTask(); 142 } 143 } 144 } 145 146 module.exports = batchingMiddleware;