Source: task-instance.js

import { BaseTaskInstance } from './external/task-instance/base';
import { TRACKED_INITIAL_INSTANCE_STATE } from './tracked-state';
import { assignProperties } from './utils';

/**
  A `TaskInstance` represent a single execution of a
  {@linkcode Task}. Every call to {@linkcode Task#perform} returns
  a `TaskInstance`.

  `TaskInstance`s are cancelable, either explicitly
  via {@linkcode TaskInstance#cancel} or {@linkcode Task#cancelAll},
  or automatically due to the host object being destroyed, or
  because concurrency policy enforced by a
  {@linkcode TaskProperty Task Modifier} canceled the task instance.

  <style>
    .ignore-this--this-is-here-to-hide-constructor,
    #TaskInstance { display: none }
  </style>

  @class TaskInstance
*/

export class TaskInstance extends BaseTaskInstance {
  setState(props) {
    let state = this._recomputeState(props);
    assignProperties(this, {
      ...props,
      isRunning: !props.isFinished,
      isDropped: state === 'dropped',
      state,
    });
  }

  _recomputeState(props) {
    if (props.isDropped) {
      return 'dropped';
    } else if (props.isCanceled) {
      if (props.hasStarted) {
        return 'canceled';
      } else {
        return 'dropped';
      }
    } else if (props.isFinished) {
      return 'finished';
    } else if (props.hasStarted) {
      return 'running';
    } else {
      return 'waiting';
    }
  }

  onStarted() {
    this.triggerEvent('started', this);
  }

  onSuccess() {
    this.triggerEvent('succeeded', this);
  }

  onError(error) {
    this.triggerEvent('errored', this, error);
  }

  onCancel(cancelReason) {
    this.triggerEvent('canceled', this, cancelReason);
  }

  formatCancelReason(reason) {
    return `TaskInstance '${this.getName()}' was canceled because ${reason}. For more information, see: http://ember-concurrency.com/docs/task-cancelation-help`;
  }

  getName() {
    if (!this.name) {
      this.name = (this.task && this.task.name) || '<unknown>';
    }
    return this.name;
  }

  selfCancelLoopWarning(parent) {
    let parentName = `\`${parent.getName()}\``;
    let childName = `\`${this.getName()}\``;
    // eslint-disable-next-line no-console
    console.warn(
      `ember-concurrency detected a potentially hazardous "self-cancel loop" between parent task ${parentName} and child task ${childName}. If you want child task ${childName} to be canceled when parent task ${parentName} is canceled, please change \`.perform()\` to \`.linked().perform()\`. If you want child task ${childName} to keep running after parent task ${parentName} is canceled, change it to \`.unlinked().perform()\``
    );
  }

  triggerEvent(...allArgs) {
    if (!this.hasEnabledEvents) {
      return;
    }

    let taskInstance = this;
    let task = taskInstance.task;
    let host = task.context;
    let eventNamespace = task && task.name;

    if (host && host.trigger && eventNamespace) {
      let [eventType, ...args] = allArgs;
      host.trigger(`${eventNamespace}:${eventType}`, ...args);
    }
  }

  /**
   * Describes the state that the task instance is in. Can be used for debugging,
   * or potentially driving some UI state. Possible values are:
   *
   * - `"dropped"`: task instance was canceled before it started
   * - `"canceled"`: task instance was canceled before it could finish
   * - `"finished"`: task instance ran to completion (even if an exception was thrown)
   * - `"running"`: task instance is currently running (returns true even if
   *     is paused on a yielded promise)
   * - `"waiting"`: task instance hasn't begun running yet (usually
   *     because the task is using the {@linkcode TaskProperty#enqueue enqueue}
   *     task modifier)
   *
   * The animated timeline examples on the [Task Concurrency](/docs/task-concurrency)
   * docs page make use of this property.
   *
   * @name state
   * @memberof TaskInstance
   * @instance
   * @readOnly
   */

  /**
   * True if the TaskInstance was canceled before it could
   * ever start running. For example, calling
   * {@linkcode Task#perform .perform()} twice on a
   * task with the {@linkcode TaskProperty#drop drop} modifier applied
   * will result in the second task instance being dropped.
   *
   * @name isDropped
   * @memberof TaskInstance
   * @instance
   * @readOnly
   */

  /**
   * True if the task is still running.
   *
   * @name isRunning
   * @memberof TaskInstance
   * @instance
   * @readOnly
   */

  /**
   * Event emitted when a new {@linkcode TaskInstance} starts executing.
   *
   * `on` from `@ember/object/evented` may be used to create a binding on the host object to the event.
   *
   * ```js
   * export default Component.extend({
   *   doSomething: task(function * () {
   *     // ... does something
   *   }),
   *
   *   onDoSomethingStarted: on('doSomething:started', function (taskInstance) {
   *     // ...
   *   })
   * });
   * ```
   *
   * @event TaskInstance#TASK_NAME:started
   * @param {TaskInstance} taskInstance - Task instance that was started
   */

  /**
   * Event emitted when a {@linkcode TaskInstance} succeeds.
   *
   * `on` from `@ember/object/evented` may be used to create a binding on the host object to the event.
   *
   * ```js
   * export default Component.extend({
   *   doSomething: task(function * () {
   *     // ... does something
   *   }),
   *
   *   onDoSomethingSucceeded: on('doSomething:succeeded', function (taskInstance) {
   *     // ...
   *   })
   * });
   * ```
   *
   * @event TaskInstance#TASK_NAME:succeeded
   * @param {TaskInstance} taskInstance - Task instance that was succeeded
   */

  /**
   * Event emitted when a {@linkcode TaskInstance} throws an an error that is
   * not handled within the task itself.
   *
   * `on` from `@ember/object/evented` may be used to create a binding on the host object to the event.
   *
   * ```js
   * export default Component.extend({
   *   doSomething: task(function * () {
   *     // ... does something
   *   }),
   *
   *   onDoSomethingErrored: on('doSomething:errored', function (taskInstance, error) {
   *     // ...
   *   })
   * });
   * ```
   *
   * @event TaskInstance#TASK_NAME:errored
   * @param {TaskInstance} taskInstance - Task instance that was started
   * @param {Error} error - Error that was thrown by the task instance
   */

  /**
   * Event emitted when a {@linkcode TaskInstance} is canceled.
   *
   * `on` from `@ember/object/evented` may be used to create a binding on the host object to the event.
   *
   * ```js
   * export default Component.extend({
   *   doSomething: task(function * () {
   *     // ... does something
   *   }),
   *
   *   onDoSomethingCanceled: on('doSomething:canceled', function (taskInstance, cancelationReason) {
   *     // ...
   *   })
   * });
   * ```
   *
   * @event TaskInstance#TASK_NAME:canceled
   * @param {TaskInstance} taskInstance - Task instance that was started
   * @param {string} cancelationReason - Cancelation reason that was was provided to {@linkcode TaskInstance#cancel}
   */

  /**
   * Cancels the task instance. Has no effect if the task instance has
   * already been canceled or has already finished running.
   *
   * @method cancel
   * @memberof TaskInstance
   * @instance
   * @async
   */

  /**
   * Returns a promise that resolves with the value returned
   * from the task's (generator) function, or rejects with
   * either the exception thrown from the task function, or
   * an error with a `.name` property with value `"TaskCancelation"`.
   *
   * @method then
   * @memberof TaskInstance
   * @instance
   * @return {Promise}
   */

  /**
   * @method catch
   * @memberof TaskInstance
   * @instance
   * @return {Promise}
   */

  /**
   * @method finally
   * @memberof TaskInstance
   * @instance
   * @return {Promise}
   */
}

if (TRACKED_INITIAL_INSTANCE_STATE) {
  Object.defineProperties(
    TaskInstance.prototype,
    TRACKED_INITIAL_INSTANCE_STATE
  );
}