summaryrefslogtreecommitdiffstats
path: root/devtools/client/netmonitor/src/middleware/batching.js
blob: c546cc2e22406238e2610236a6baf7f31635773c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/* 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,
  BATCH_ENABLE,
  BATCH_RESET,
  BATCH_FLUSH,
} = require("resource://devtools/client/netmonitor/src/constants.js");

const REQUESTS_REFRESH_RATE = 50; // ms

/**
 * Middleware that watches for actions with a "batch = true" value in their meta field.
 * These actions are queued and dispatched as one batch after a timeout.
 * Special actions that are handled by this middleware:
 * - BATCH_ENABLE can be used to enable and disable the batching.
 * - BATCH_RESET discards the actions that are currently in the queue.
 */
function batchingMiddleware() {
  return next => {
    let queuedActions = [];
    let enabled = true;
    let flushTask = null;

    return action => {
      if (action.type === BATCH_ENABLE) {
        return setEnabled(action.enabled);
      }

      if (action.type === BATCH_RESET) {
        return resetQueue();
      }

      if (action.type === BATCH_FLUSH) {
        return flushQueue();
      }

      if (action.meta?.batch) {
        if (!enabled) {
          next(action);
          return Promise.resolve();
        }

        queuedActions.push(action);

        if (!flushTask) {
          flushTask = new DelayedTask(flushActions, REQUESTS_REFRESH_RATE);
        }

        return flushTask.promise;
      }

      return next(action);
    };

    function setEnabled(value) {
      enabled = value;

      // If disabling the batching, flush the actions that have been queued so far
      if (!enabled && flushTask) {
        flushTask.runNow();
      }
    }

    function resetQueue() {
      queuedActions = [];

      if (flushTask) {
        flushTask.cancel();
        flushTask = null;
      }
    }

    function flushQueue() {
      if (flushTask) {
        flushTask.runNow();
      }
    }

    function flushActions() {
      const actions = queuedActions;
      queuedActions = [];

      next({
        type: BATCH_ACTIONS,
        actions,
      });

      flushTask = null;
    }
  };
}

/**
 * Create a delayed task that calls the specified task function after a delay.
 */
function DelayedTask(taskFn, delay) {
  this._promise = new Promise((resolve, reject) => {
    this.runTask = cancel => {
      if (cancel) {
        reject("Task cancelled");
      } else {
        taskFn();
        resolve();
      }
      this.runTask = null;
    };
    this.timeout = setTimeout(this.runTask, delay);
  }).catch(console.error);
}

DelayedTask.prototype = {
  /**
   * Return a promise that is resolved after the task is performed or canceled.
   */
  get promise() {
    return this._promise;
  },

  /**
   * Cancel the execution of the task.
   */
  cancel() {
    clearTimeout(this.timeout);
    if (this.runTask) {
      this.runTask(true);
    }
  },

  /**
   * Execute the scheduled task immediately, without waiting for the timeout.
   * Resolves the promise correctly.
   */
  runNow() {
    clearTimeout(this.timeout);
    if (this.runTask) {
      this.runTask();
    }
  },
};

module.exports = batchingMiddleware;