1
\$\begingroup\$

Batch processing steps:

  1. When a job is posted, push it to the queue.
  2. Get the required number of items from the queue and put it in the batch.
  3. If a batch was processed before this, wait till BATCH_INTERVAL time has passed.
  4. Process the batch.
  5. Clear the batch.
  6. Monitor the queue. Back to step 2.

batcher.js

const QUEUE_MONITORING_INTERVAL = 200;
const BATCH_SIZE = 3;
const BATCH_INTERVAL = 1000;
const PROCESSING = "processing";
const IDLE = "idle";

const queue = [];
const batch = [];
let queueInterval = null;
let lastBatchProcessing = null;
let batchStatus = IDLE;

function pushJob(fn) {
  const newPromise = new Promise((resolve, reject) => {
    const job = createJob(fn, resolve, reject);
    queue.push(job);
  });

  monitorQueue();

  return newPromise;
}

function createJob(fn, resolve, reject) {
  return {
    fn,
    resolve,
    reject,
  };
}

function monitorQueue() {
  if (queueInterval == null) {
    queueInterval = setInterval(controller, QUEUE_MONITORING_INTERVAL);
  }
}

function controller() {
  handleQueue();
  handleBatch();
}

function handleQueue() {
  if (batchStatus !== IDLE) {
    return;
  } else if (shouldStopMonitoringQueue()) {
    stopMonitoringQueue();
  } else if (queue.length && batch.length < BATCH_SIZE) {
    pushToBatch();
  }
}

function shouldStopMonitoringQueue() {
  return (
    queue.length === 0 &&
    batch.length === 0 &&
    Date.now() - lastBatchProcessing > BATCH_INTERVAL
  );
}

function stopMonitoringQueue() {
  clearInterval(queueInterval);
  queueInterval = null;
}

function pushToBatch() {
  const spaceRemaining = BATCH_SIZE - batch.length;
  const jobsToPop = Math.min(queue.length, spaceRemaining);
  const jobs = queue.splice(0, jobsToPop);
  batch.push(...jobs);
}

function handleBatch() {
  if (shouldProcessBatch()) {
    processBatch();
  }
}

function shouldProcessBatch() {
  if (!(batch.length && batchStatus === IDLE)) {
    return false;
  }

  return (
    lastBatchProcessing == null ||
    Date.now() - lastBatchProcessing > BATCH_INTERVAL
  );
}

async function processBatch() {
  batchStatus = PROCESSING;
  const promises = getPromisesFromBatch();
  const results = await Promise.allSettled(promises);
  processResults(results);

  clearBatch();
  batchStatus = IDLE;
  lastBatchProcessing = Date.now();
}

function getPromisesFromBatch() {
  const promises = [];

  for (const job of batch) {
    const promise = Promise.resolve(job.fn());
    promises.push(promise);
  }

  return promises;
}

function processResults(results) {
  for (let i = 0; i < results.length; i++) {
    const result = results[i];
    const job = batch[i];

    if (result.status === "fulfilled") {
      job.resolve(result.value);
    } else {
      job.reject(result.reason);
    }
  }
}

function clearBatch() {
  batch.splice(0, batch.length);
}

module.exports = {
  pushJob,
};

test.js

const batcher = require("./batcher");

async function main() {
  const start = Date.now();
  const promises = [];

  for (let i = 0; i < 20; i++) {
    const promise = batcher.pushJob(async () => await fn());
    promises.push(promise);
  }

  const results = await Promise.all(promises);
  const timeElapsed = (Date.now() - start) / 1000;

  console.log(results);
  console.log("Time elapsed: ", timeElapsed);
}

async function fn() {
  return new Promise((resolve) => setTimeout(() => resolve(Math.random()), 3000));
}

main();

\$\endgroup\$
6
  • \$\begingroup\$ What's the question? Be specific, not just 'how can I improve it'. What do you think needs improving etc. NB: Your test isn't very relevant, as all jobs have the same runtime and therefore completion order. It's not validating anything. \$\endgroup\$ Jul 21, 2022 at 9:01
  • \$\begingroup\$ @DaveMeehan There doesn't need to be a question. This is Code Review, not Stack Overflow. People post their code to get feedback. Are you new here? \$\endgroup\$
    – sg7610
    Jul 21, 2022 at 15:59
  • \$\begingroup\$ Oh I don’t know, you could look at my reputation or activity to figure out that, or just make unhelpful, vaguely insulting comments. Without a basic steer how are we supposed to know what you want feedback on? \$\endgroup\$ Jul 21, 2022 at 18:06
  • \$\begingroup\$ @DaveMeehan You would not ask this question if you are familiar with Code Review. Code review encompasses everything from naming variable, code formatting, design, readability, extensibility, etc. You can read more about it here. Here is another code review post I made. Do you see any questions there? \$\endgroup\$
    – sg7610
    Jul 22, 2022 at 4:24
  • \$\begingroup\$ Thanks. For the record I did spend time reviewing your code, but I felt it relevant to ask the question I did so that I could give the right sort of feedback. The outline at the start is so brief and leaves a lot of questions. The code as presented leaves more questions. If you want a conversation, engage in a conversation. If you want to act superior and entitled, then I’m afraid I’m not going to waste my time. \$\endgroup\$ Jul 22, 2022 at 7:39

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.