Source: app/util/KeyedTaskQueue.js

import debugLib from 'debug';

const debug = debugLib('SlimmingWorld:KeyedTaskQueue');

/**
 * Util class that maintains queues for asynchronous tasks for each key assigned to a task.
 */
class KeyedTaskQueue {
  /**
   * Map of promises of running tasks for each key.
   */
  queues = {};
  /**
   * Contains a symbol identifying the task that was last added to the queue.
   * Used to determine if we should clear the queue after the task completes.
   */
  lastInQueue = {};

  /**
   * Queues a new task
   * @param generator {function} A function that returns a Promise (the task to be executed)
   * @param key {string} The key that this task should queue under. If more tasks with the same
   * key are already running (or queued), this task will execute after they resolve
   * @returns {Promise} A promise that resolves with the result of the generator function
   */
  queue(generator, key) {
    const taskId = Symbol(`Task ${key}`);
    const shortKey = key.substring(0, 70);

    const promise = (async () => {
      const currentItem = this.queues[key];
      if (currentItem) {
        try {
          debug(`Waiting for running tasks with key "${shortKey}..."`);
          await currentItem;
        } catch (e) {
          // ignore errors in the queue, make sure they still continue.
        }
      }

      debug(`Executing task with key "${shortKey}..."`);
      const result = await generator();

      if (this.lastInQueue[key] === taskId) {
        debug(`Clearing queue for key "${shortKey}..."`);
        delete this.queues[key];
        delete this.lastInQueue[key];
      }

      return result;
    })();

    this.queues[key] = promise;
    this.lastInQueue[key] = taskId;

    return promise;
  }
}

export default KeyedTaskQueue;