utils/subscriptions.js

/*
 * This file is part of AUX.
 *
 * AUX is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * AUX is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General
 * Public License along with this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

/**
 * Utilities for handling subscriptions.
 *
 * @module utils/subscriptions
 */

import { warn } from './log.js';
import { typecheckFunction } from './typecheck.js';

export function initSubscriptions() {
  return null;
}

function copy(sub) {
  if (Array.isArray(sub)) return sub.slice(0);
  return sub;
}

/**
 * Adds the Subscription(s) subscription to the Subscriptions subscriptions.
 * This function returns the new subscriptions. It may modify the first
 * argument.
 */
export function addSubscription(subscriptions, subscription) {
  if (subscriptions === null) {
    return copy(subscription);
  } else if (Array.isArray(subscriptions)) {
    if (Array.isArray(subscription)) {
      return subscriptions.concat(subscription);
    }

    if (subscription !== null) {
      typecheckFunction(subscription);
      subscriptions.push(subscription);
    }

    return subscriptions;
  } else {
    typecheckFunction(subscriptions);

    if (Array.isArray(subscription)) {
      return [subscriptions].concat(subscription);
    }

    if (subscription !== null) {
      typecheckFunction(subscription);
      return [subscriptions, subscription];
    }

    return subscriptions;
  }
}

function safeCall(cb) {
  try {
    cb();
  } catch (err) {
    warn('Unsubscription handler threw an exception: %o', err);
  }
}

/**
 * Unsubscribe all subscriptions.
 */
export function unsubscribeSubscriptions(subscriptions) {
  if (subscriptions === null) return null;

  if (Array.isArray(subscriptions)) {
    for (let i = 0; i < subscriptions.length; i++) {
      safeCall(subscriptions[i]);
    }
  } else {
    safeCall(subscriptions);
  }

  return null;
}

/**
 * Represents a Subscription.
 */
export class Subscription {
  constructor(subscription) {
    if (subscription === void 0) {
      this.sub = initSubscriptions();
    } else if (
      typeof subscription === 'function' ||
      Array.isArray(subscription)
    ) {
      this.sub = subscription;
    } else {
      if (
        typeof subscription === 'object' &&
        typeof subscription.unsubscribe === 'function'
      ) {
        this.sub = subscription.unsubscribe.bind(subscription);
      } else {
        throw new TypeError('Unsupported argument.');
      }
    }
  }

  /**
   * Unsubscribe from all subscriptions.
   */
  unsubscribe() {
    this.sub = unsubscribeSubscriptions(this.sub);
  }

  /**
   * A binary flag which indicates that this subscription has
   * already been unsubscribed.
   */
  get closed() {
    return this.sub === null;
  }
}

/**
 * Represents a set of Subscriptions.
 */
export class Subscriptions extends Subscription {
  /**
   * Add a subscription.
   */
  add(subscription) {
    if (
      typeof subscription === 'object' &&
      typeof subscription.unsubscribe === 'function'
    ) {
      subscription = subscription.unsubscribe.bind(subscription);
    }

    this.sub = addSubscription(this.sub, subscription);
  }
}

export function combineSubscriptions(...args) {
  let subscriptions = initSubscriptions();

  for (let i = 0; i < args.length; i++)
    subscriptions = addSubscription(subscriptions, args[i]);

  return () => {
    subscriptions = unsubscribeSubscriptions(subscriptions);
  };
}