Home Reference Source

src/chord.js

'use strict';

import Base from './base';
import Constants from './constants';
import Immutable from 'immutable';

export default class Chord extends Base {
  constructor(fns) {
    super();
    this.__fns = Array.prototype.slice.call(fns);
    this.__doneFns = new Immutable.Set();
    this.__currentFn = -1;
    this.__changeEvent = 'chord-change';
  }

  __doStep(step) {
    this.__emit(Constants.StepStatus.STARTED, null, step);
    let stepNum = this.__currentFn;
    if (step instanceof Base) {
      step.on(Constants.States.FINISHED, () => {
        this.finishStep(step, stepNum);
      });
      step.on(Constants.States.FAILED, () => {
        let error = step.getState('error');
        if (error == null) {
          error = step.getState('errors', `step ${step.name} failed`);
        }
        this.finishStep(step, stepNum, error);
      });
      // Going to the next step is safer than calling start
      // because the Flow could have already started.
      step.gotoNextStep();
    }
    else {
      try {
        step(
          this,
          () => this.finishStep(step, stepNum),
          (e) => this.finishStep(step, stepNum, e)
        );
      }
      catch (e) {
        this.finishStep(step, stepNum, e);
      }
    }
    this.gotoNextStep();
  }

  __handleError(step, error) {
    let errors = this.getState('errors', new Immutable.List());
    errors = errors.push(error);
    this.setState({errors});
    this.failStep(step);
  }

  __gotoNextStep() {
    if (this.hasNextStep()) {
      this.__currentFn = this.__currentFn + 1;
    }
  }

  addStep(step) {
    this.__fns.push(step);
  }

  failStep(step) {
    this.__emit(Constants.StepStatus.FAILED, null, step);

    if (this.isFinished() && this.getState('errors').size === this.__fns.length) {
      this.__readyState = this.__readyState.add(
        Constants.States.FAILED
      );
      this.__emit(Constants.States.FAILED, Constants.States.FAILED, step);
    }
  }

  finishStep(step, stepNum, opt_error) {
    if (this.__fns[stepNum] !== step) {
      throw `Got incorrect step num '${stepNum}' for step`;
    }
    this.__doneFns = this.__doneFns.add(stepNum);

    if (opt_error != null) {
      this.__handleError(step, opt_error);
    }
    else {
      this.__emit(Constants.StepStatus.FINISHED, null, step);
    }

    let isFinished = this.__fns.every((_, i) => {
      return this.__doneFns.has(i);
    });

    if (isFinished) {
      this.finish();
    }
  }

  getCurrentFn() {
    if (this.__currentFn > -1 && this.__currentFn < this.__fns.length) {
      return this.__fns[this.__currentFn];
    }
    return null;
  }

  gotoNextStep() {
    if (!this.isStarted()) {
      this.start();
      return;
    }

    if(this.hasNextStep()) {
      this.__gotoNextStep();
      // The naming here really sucks.
      // With a Chord there is no concept of a "current function" because
      // functions could be async. This needs a better name.
      let step = this.getCurrentFn();
      this.__doStep(step);
    }
  }

  hasNextStep() {
    return (!this.isFinished() &&
            !this.isLastStep() &&
            this.__currentFn < this.__fns.length);
  }

  isFinished() {
    let isFinished = this.__fns.every((_, i) => {
      return this.__doneFns.has(i);
    });
    return isFinished;
  }

  isLastStep() {
    return this.__currentFn === this.__fns.length -1;
  }
}