Learn about our RFC process, Open RFC meetings & more.Join in the discussion! »

interruptible-tasks

1.1.0 • Public • Published

Build Status Build Status Coverage Status

Interruptible-tasks

Interruptible Tasks is a JavaScript library for creating manageable, interruptible and cancelable async functions - Tasks.

The Why

This library was created in order to manage complex async flow without pain.

If you have to know execution status of some async function, usually you will finish up with adding manual "status" updates inside of function. If your function could throw exception during it's execution, you need to take care about it too.

Once you needed ability to cancel or interrupt function you'll have to add more and more code into each place and take care of errors handling too.

Interruptible Tasks library makes you free of that routine and provides some extra features described below.

Features

  • Tasks could be cancelable, interruptible and vice-versa (details below)
  • Tasks could be nested
  • Tasks status changes (pending/stopped) could be easily connected to any state library (redux/vuex/hyperapp/custom /etc)
  • Clear errors handling and custom error types for granular control over your code
  • Heavily tested
  • ES5/ES3 support - goes bundled as CommonJS, ES6 module and IIFE

You could adopt your existing code step-by-step, without any huge refactoring.

Getting Started

Installing

yarn add interruptible-tasks
// or
npm install interruptible-tasks

Using

Basic usage

Here is example of very basic usage. API in details is described below.

import { createTask } from 'interruptible-tasks';
 
const demoFunction = function*(value) {
  yield new Promise(resolve => setTimeout(() => resolve(value), 1000));
};
const task = createTask(demoFunction);
 
task.run('Here we go!'); // resolves with "Here we go!" after 1 second

API

createTask(generator, params, connect)

Parameters

generator - required - your generator function to be converted to Task.

If you are modifying existing code, then you have to change type of your function from async to generator and replace all await statements with yield.

Example:

// before
const demoFunction = async function(value) {
  await new Promise(resolve => setTimeout(() => resolve(value), 1000));
};
// after
const demoFunction = function*(value) {
  yield new Promise(resolve => setTimeout(() => resolve(value), 1000));
};

params - optional - object with parameters for your Task.

Defaults:

({
  interruptible: false,
  cancelable: true,
  name: Symbol('Unnamed task')
});

You can redefine any of those parameters. Explanation:

interruptible - can your running Task be interrupted by new .run() call or not. Interrupting means that currently running Task will reject its promise when next yield statement inside generator function is reached.

Example:

const task = createTask(
  function*(data) {
    yield new Promise(resolve => setTimeout(resolve, 10));
    yield data;
  },
  { interruptible: true, cancelable: false, name: 'demoTask' }
);
 
task.run('not ok').catch(console.error); // after 10ms: TaskHasBeenInterruptedError('Task demoTask has been interrupted')
 
const result = await task.run('ok'); // after 10ms: 'ok'

If interruptible: false is passed, second .run('ok') call will be rejected immediately with NotInterruptibleError, while first .call('not ok') will be resolved normally.

cancelable - can your running Task be cancelled by .cancel() call or not. Cancelable means that currently running Task will reject its promise when next yield statement inside generator function is reached.

Example:

const task = createTask(
  function*(data) {
    yield new Promise(resolve => setTimeout(resolve, 10));
    yield data;
  },
  { interruptible: false, cancelable: true, name: 'demoTask' }
);
 
task.run().catch(console.error); // after 10ms: TaskHasBeenCancelledError('Task demoTask has been cancelled')
task.cancel(); // true

If cancelable: false is passed, .cancel() call will be rejected immediately with NotCancelableError.

name - name of your Task to be passed to connect function. If you do not provide name, default name will be used. It does not affect any other behaviour. name could be of any data type.

connect - optional - function to be used for passing Task status updates (pending/stopped).

Example:

import { createTask, taskStatuses } from 'interruptible-tasks';
const taskName = 'connectedTask';
const stateImitation = new Map();
 
const connect = (name, status) => {
  stateImitation.set(name, status);
};
 
const task = createTask(
  function*() {
    yield new Promise(resolve => setTimeout(resolve, 10));
  },
  { interruptible: false, cancelable: false, name: taskName },
  connect
);
 
let runPromise = task.run('some data');
console.log(stateImitation.get(taskName) === taskStatuses.pending); // === true;
await runPromise;
console.log(stateImitation.get(taskName) === taskStatuses.stopped); // === true;
Returns

createTask function returns an object (Task) containing two properties: run and cancel functions.

Task.run and Task.cancel

run function returns a Promise. run accepts any amount of arguments to be passed to generator function. It will be resolved with value returned by generator function once it is finished. It will be rejected with custom error if Task is interrupted or cancelled. It will be rejected if any other error occurs inside generator function.

cancel function return true if Task could and will be cancelled, otherwise it will throw a custom error.

Errors

There are four custom errors exported:

NotInterruptibleError is being threw when .run() is called on non-interruptible Task.

NotCancelableError is being threw when .cancel() is called on non-cancelable Task.

TaskHasBeenInterruptedError is being threw when already ran Task is interrupted by .run().

TaskHasBeenCancelledError is being threw when Task is cancelled by .cancel().

Task statuses

Task could be in one of two statuses: pending or stopped. Statuses are presented as Symbols, so object taskStatuses needs to be imported to understand current status of your Task.

Example:

import { taskStatuses } from 'interruptible-tasks';
 
console.log(taskStatuses);
/*
{
  pending: Symbol('pending'),
  stopped: Symbol('stopped')
}
*/

Usage example could be found above inside of connect description.

Nesting

Tasks could be nested, in that case cancellation and interruption calls will bubble down to every nested Task. If any of nested Tasks is rejected by any reason, parent Task will receive corresponding error. Cancellation or interruption of nested Task does not affect parent Task if it happened on nested Task only.

Examples are available in __tests__ folder at GitHub repo.

Using specific build

Library goes with four builds bundled, default build is CommonJS module with code transpiled to ES5. If you need to use another build, you could specify it in import:

import {} from 'interruptible-tasks'; // CommonJS, ES5
import {} from 'interruptible-tasks/index.es5.es.js'; // ES module, ES5
import {} from 'interruptible-tasks/index.es5.iife.js'; // IIFE, ES5
import {} from 'interruptible-tasks/index.es3.cjs.js'; // CommonJS, ES3

More examples

There are a lot of good examples in tests, you can find them at GitHub repo in __tests__ folder.

Authors

License

This project is licensed under the MIT License - see the LICENSE.md file for details.

Install

npm i interruptible-tasks

DownloadsWeekly Downloads

10

Version

1.1.0

License

MIT

Unpacked Size

24.9 kB

Total Files

9

Last publish

Collaborators

  • avatar