Task

Tasks model asynchronous processes with automatic resource handling. They are generally constructed with the task function.

This feature is experimental!

This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.

Signature

Task(computation)
forall value, reason:
  new (
    ({
       resolve: (value) => Void,
       reject: (reason) => Void,
       cancel: () => Void,
       cleanup: (() => Void) => Void,
       onCancelled: (() => Void) => Void,
       get isCancelled: Boolean
     }) => Void
  ) => Task value reason

Documentation

Tasks model asynchronous processes with automatic resource handling. They are generally constructed with the task function.

See the documentation for folktale/concurrency/task for more details.

Static properties

Constructing

of(value)

Constructs a Task that resolves with a successful value.

Experimental
rejected(reason)

Constructs a Task that resolves with a rejected value.

Experimental

Fantasy Land

ap(that)

Part of the Applicative instance for Fantasy Land 1.x. See the apply method for details.

fantasy-land/ap(that)

Part of the Applicative instance for Fantasy Land 2.x+. See the apply method for details.

fantasy-land/of(value)

Part of the Applicative instance for Fantasy Land 2.x+. See the of method for details.

Special values

prototype

The container for instance methods for the Task structure.

Experimental

Instance (prototype) properties

Combining tasks

and: value(that)

Constructs a new task that awaits both tasks to resolve. The result of the new task is a tuple containing the results of the left and right tasks, if they all succeed, or the first failure if they fail.

Experimental
or: value(that)

Combines two tasks such that the resulting task assimilates the result of the first one to resolve.

Experimental

Executing tasks

run: value()

Executes a Task and returns a TaskExecution object representing the execution.

Experimental

Fantasy Land

ap(that)

Part of the Applicative instance for Fantasy Land 1.x. See the apply method for details.

fantasy-land/ap(that)

Part of the Applicative instance for Fantasy Land 2.x+. See the apply method for details.

fantasy-land/bimap(f, g)

Part of the Bifunctor instance for Fantasy Land 2.x+. See the bimap method for details.

fantasy-land/chain(transformation)

Part of the Monad instance for Fantasy Land 2.x+. See the chain method for details.

fantasy-land/map(transformation)

Part of the Functor instance for Fantasy Land 2.x+. See the map method for details.

Pattern matching

willMatchWith: value(pattern)

Chooses and executes a function for each variant in a Task. The function must return a new task, whose value and state will be assimilated.

Experimental

Recovering from errors

orElse: value(handler)

Transforms a failed task's value and state.

Experimental
swap: value()

Inverts the state of a Task. That is, turns successful tasks into failed ones, and failed tasks into successful ones.

Experimental

Transforming tasks

apply: value(task)

Applies the function in the left task to the value on the right task. The left task is ran to completion before the right task is started.

Experimental
bimap: value(rejectionTransformation, successTransformation)

Transforms the rejected or resolved values of a Task with a function. The state of the task is not changed.

Experimental
chain: value(transformation)

Transforms the value and state of a Task.

Experimental
map: value(transformation)

Transforms the value of a successful task.

Experimental
mapRejected: value(transformation)

Transforms the value of a failed task.

Experimental

Types

constructor: Task(computation)

Tasks model asynchronous processes with automatic resource handling. They are generally constructed with the task function.

Experimental

Source Code

Defined in source/concurrency/task/_task.js at line 20, column 0
class Task {
  /*~
   * stability: experimental
   * type: |
   *   forall value, reason:
   *     new (
   *       ({
   *          resolve: (value) => Void,
   *          reject: (reason) => Void,
   *          cancel: () => Void,
   *          cleanup: (() => Void) => Void,
   *          onCancelled: (() => Void) => Void,
   *          get isCancelled: Boolean
   *        }) => Void
   *     ) => Task value reason
   */
  constructor(computation) {
    this._computation = computation;
  }

  /*~
   * stability: experimental
   * type: |
   *   forall e, v1, v2:
   *     (Task e v1).((v1) => Task e v2) => Task e v2
   */
  chain(transformation) {
    return new Task(resolver => {
      const execution = this.run();
      resolver.onCancelled(() => execution.cancel());

      execution.listen({
        onCancelled: resolver.cancel,
        onRejected:  resolver.reject,
        onResolved:  value => {
          execution.link(transformation(value).run().listen({
            onCancelled: resolver.cancel,
            onRejected:  resolver.reject,
            onResolved:  resolver.resolve
          }));
        }
      });
    });
  }

  /*~
   * stability: experimental
   * type: |
   *   forall e, v1, v2:
   *     (Task e v1).((v1) => v2) => Task e v2
   */
  map(transformation) {
    return new Task(resolver => {
      const execution = this.run();
      resolver.onCancelled(() => execution.cancel());

      execution.listen({
        onCancelled: resolver.cancel,
        onRejected:  resolver.reject,
        onResolved:  value => resolver.resolve(transformation(value))
      });
    });
  }

  /*~
   * stability: experimental
   * type: |
   *   forall e1, e2, v:
   *     (Task e1 v).((e1) => e2) => Task e2 v
   */
  mapRejected(transformation) {
    return new Task(resolver => {
      const execution = this.run();
      resolver.onCancelled(() => execution.cancel());

      execution.listen({
        onCancelled: resolver.cancel,
        onRejected:  reason => resolver.reject(transformation(reason)),
        onResolved:  resolver.resolve
      });
    });
  }

  /*~
   * stability: experimental
   * type: |
   *   forall e, v1, v2:
   *     (Task e ((v1) => v2)).(Task e v1) => Task e v2
   */
  apply(task) {
    return this.chain(f => task.map(f));
  }

  /*~
   * stability: experimental
   * type: |
   *   forall e1, e2, v1, v2:
   *     (Task e1 v1).((e1) => e2, (v1) => v2) => Task e2 v2
   */
  bimap(rejectionTransformation, successTransformation) {
    return new Task(resolver => {
      const execution = this.run();
      resolver.onCancelled(() => execution.cancel());

      execution.listen({
        onCancelled: resolver.cancel,
        onRejected:  reason => resolver.reject(rejectionTransformation(reason)),
        onResolved:  value => resolver.resolve(successTransformation(value))
      });
    });
  }

  /*~
   * stability: experimental
   * type: |
   *   forall e1, e2, v1, v2:
   *     type Pattern = { row |
   *       Cancelled: ()  => Task e2 v2,
   *       Resolved:  (b) => Task e2 v2,
   *       Rejected:  (a) => Task e2 v2
   *     }
   *
   *     (Task e1 v1).(Pattern) => Task e2 v2
   */
  willMatchWith(pattern) {
    return new Task(resolver => {
      const execution = this.run();
      resolver.onCancelled(() => execution.cancel());
      
      const resolve = (handler) => (value) => execution.link(handler(value).run().listen({
        onCancelled: resolver.cancel,
        onRejected:  resolver.reject,
        onResolved:  resolver.resolve
      }));
      execution.listen({
        onCancelled: resolve(_ => pattern.Cancelled()),
        onRejected:  resolve(pattern.Rejected),
        onResolved:  resolve(pattern.Resolved)
      });
    });
  }

  /*~
   * stability: experimental
   * type: |
   *   forall e, v: (Task e v).() => Task v e
   */
  swap() {
    return new Task(resolver => {
      let execution = this.run();   // eslint-disable-line prefer-const
      resolver.onCancelled(() => execution.cancel());

      execution.listen({
        onCancelled: resolver.cancel,
        onRejected:  resolver.resolve,
        onResolved:  resolver.reject
      });
    });
  }

  /*~
   * stability: experimental
   * type: |
   *   forall e, e2, v:
   *     (Task e v).((e) => Task e2 v) => Task e2 v
   */
  orElse(handler) {
    return new Task(resolver => {
      const execution = this.run();
      resolver.onCancelled(() => execution.cancel());

      execution.listen({
        onCancelled: resolver.cancel,
        onResolved:  resolver.resolve,
        onRejected:  reason => {
          execution.link(handler(reason).run().listen({
            onCancelled: resolver.cancel,
            onRejected:  resolver.reject,
            onResolved:  resolver.resolve
          }));
        }
      });
    });
  }


  /*~
   * stability: experimental
   * type: |
   *   forall e, v:
   *     (Task e v).(Task e v) => Task e v
   */
  or(that) {
    return new Task(resolver => {
      let thisExecution = this.run();   // eslint-disable-line prefer-const
      let thatExecution = that.run();   // eslint-disable-line prefer-const
      let done = false;

      resolver.onCancelled(() => {
        thisExecution.cancel();
        thatExecution.cancel();
      });

      const guard = (fn, execution) => (value) => {
        if (!done) {
          done = true;
          execution.cancel();
          fn(value);
        }
      };

      thisExecution.listen({
        onRejected:  guard(resolver.reject, thatExecution),
        onCancelled: guard(resolver.cancel, thatExecution),
        onResolved:  guard(resolver.resolve, thatExecution)
      });

      thatExecution.listen({
        onRejected:  guard(resolver.reject, thisExecution),
        onCancelled: guard(resolver.cancel, thisExecution),
        onResolved:  guard(resolver.resolve, thisExecution)
      });
    });
  }

  /*~
   * stability: experimental
   * type: |
   *   forall e, v1, v2:
   *     (Task e v1).(Task e v2) => Task e (v1, v2)
   */
  and(that) {
    return new Task(resolver => {   // eslint-disable-line max-statements
      let thisExecution = this.run();   // eslint-disable-line prefer-const
      let thatExecution = that.run();   // eslint-disable-line prefer-const
      let valueLeft = null;
      let valueRight = null;
      let doneLeft = false;
      let doneRight = false;
      let cancelled = false;

      resolver.onCancelled(() => {
        thisExecution.cancel();
        thatExecution.cancel();
      });

      const guardResolve = (setter) => (value) => {
        if (cancelled)  return;

        setter(value);
        if (doneLeft && doneRight) {
          resolver.resolve([valueLeft, valueRight]);
        }
      };

      const guardRejection = (fn, execution) => (value) => {
        if (cancelled)  return;

        cancelled = true;
        execution.cancel();
        fn(value);
      };

      thisExecution.listen({
        onRejected:  guardRejection(resolver.reject, thatExecution),
        onCancelled: guardRejection(resolver.cancel, thatExecution),
        onResolved:  guardResolve(x => {
          valueLeft = x;
          doneLeft = true;
        })
      });

      thatExecution.listen({
        onRejected:  guardRejection(resolver.reject, thisExecution),
        onCancelled: guardRejection(resolver.cancel, thisExecution),
        onResolved:  guardResolve(x => {
          valueRight = x;
          doneRight = true;
        })
      });
    });
  }

  /*~
   * stability: experimental
   * type: |
   *   forall e, v: (Task e v).() => TaskExecution e v
   */
  run() {
    let deferred = new Deferred();    // eslint-disable-line prefer-const
    let cleanups      = [];
    let cancellations = [];
    let isCancelled   = false;
    let done          = false;

    deferred.listen({
      onCancelled: _ => {
        done = true;
        isCancelled = true;
        cancellations.forEach(f => f());
        cleanups.forEach(f => f());
        cancellations = [];
        cleanups = [];
      },

      onResolved: _value => {
        done = true;
        cleanups.forEach(f => f());
        cleanups = [];
        cancellations = [];
      },

      onRejected: _reason => {
        done = true;
        cleanups.forEach(f => f());
        cleanups = [];
        cancellations = [];
      }
    });

    const execution = new TaskExecution(this, deferred);

    const resources = this._computation({
      reject:  error => { deferred.reject(error) },
      resolve: value => { deferred.resolve(value) },
      cancel:  _     => { deferred.maybeCancel() },

      get isCancelled() { return isCancelled },
      cleanup(f) {
        if (done) {
          throw new Error('Can\'t attach a cleanup handler after the task is settled.');
        }
        cleanups.push(f);
      },
      onCancelled(f) {
        if (done) {
          throw new Error('Can\'t attach a cancellation handler after the task is settled.');
        }
        cancellations.push(f);
      }
    });

    return execution;
  }
}
Stability
experimental
Licence
MIT
Module
folktale/concurrency/task/_task
Authors
Copyright
(c) 2013-2017 Quildreen Motta, and CONTRIBUTORS
Authors
  • Quildreen Motta
Maintainers
  • Quildreen Motta <queen@robotlolita.me> (http://robotlolita.me/)