@drupe/tasks

Library for combining complex tasks.

Stats

StarsIssuesVersionUpdatedCreatedSize
@drupe/tasks
0.6.03 years ago3 years agoMinified + gzip package size for @drupe/tasks in KB

Readme

@drupe/tasks

Library for combining complex tasks.

Installation

npm i @drupe/tasks

Tasks

Tasks are async functions that can do anything. Each task is executed in it's own context and can "use" other tasks as dependencies. In addition, tasks can implement functionality like watching for input changes to invalidate themselves or all dependent tasks to re-run invalidated tasks.

The task context is passed to the function with the first argument:

async function foo(task) {
    // TODO: Do something...
    return 'bar'
}

task.runner

Get the current runner instance.

task.args

Get the object that was passed via the runner's args option.
This is a shortcut for task.runner.args.

task.passed

Get the value that was passed to this task by it's predecessor. If no value was passed, it is undefined.

task.use(fn)

Use another task as dependency.
If a task is used multiple times, it is still only executed once, except when it has previously been invalidated.

async function foo() {
    return 'bar'
}

async function bar(task) {
    await task.use(foo) // -> 'bar'
}
  • fn <function> - The task function to use as dependency.
  • returns <Promise> - The promise returned by the dependency task.

task.pass(value)

Pass a value to the descendant of this task. This value will be available via task.passed.

async function foo(task) {
    let reRuns = task.passed || 0

    task.pass(reRuns + 1)
}

task.push(promise, options)

Use a promise for the next execution of this task instead of running it again.
This is useful if you want to embed a process in a task that re-runs itself.
Pushing a new state also invalidates all dependents.

// This task will return 'foo' and 'bar' with the second executions without re-running.
async function foo(task) {
    setTimeout(() => {
        task.push(Promise.resolve('bar'))
    }, 100)

    return 'foo'
}
  • promise <Promise> - The promise to use for the next execution.
  • options <object> - Optional. An object with the following options:
    • wrap <boolean> - True to wrap the promise into a WrappedPromise (as documented below) to prevent unhandled rejections.
task.pushResolve(value, options)
// is the same as:
task.push(Promise.resolve(value), options)

task.pushReject(value, options)
// is the same as:
task.push(Promise.reject(value), options)

task.invalidate()

Invalidate the task and all it's dependents.

async function foo(task) {
    // At any time while or after executing the task:
    task.invalidate()
}

task.invalidateDependents()

Invalidate only the dependents of this task.

async function foo(task) {
    // At any time while or after executing the task:
    task.invalidateDependents()
}

event: dispose

The dispose event is emitted when the task is invalidated and can be used to cleanup resources like file system watchers.

async function foo(task) {
    task.on('dispose', push => {
        push(promise)
    })
}
  • push <function> - A function to push an additional promise to await before the invalidation process is complete. This can be used to cleanup resources asynchronously.

Runner

The runner class is responsible for running tasks and keeping track of their contexts.

const {Runner} = require('@drupe/tasks')

new Runner(entry[, options])

Create a new runner instance.

const runner = new Runner(entry, options)
  • entry <function> - The entry task.
  • options <object> - Optional. An object with the following options:
    • args <object> - An object that is passed to tasks with the task.args property. Defaults to {}

runner.args

Get the object that was passed with the args option.

runner.run()

Run the entry task.
If the entry task has been run before and is still valid, the entry task will not be re-run.

await runner.run()
  • returns <Promise> - The promise returned by the entry task.

runner.dispose(options)

Invalidate all tasks without re-running the entry task. Note that the runner can still be activated by calling run() again.

await runner.dispose({
    clearPassed: false
})
  • options <object> - Optional. An object with the following options:
    • clearPassed <boolean> - True to clear values passed to future tasks. Default is false.
  • returns <Promise> - A promise that resolves when the invalidation is complete.

event: resolve

The resolve event is emitted when the entry task has been invalidated and re-run successfully.

runner.on('resolve', result => ...)
  • result <any> - The value returned by the task.

event: reject

The reject event is emitted when the entry task has been invalidated and failed to re-run.

runner.on('reject', err => ...)
  • err <any> - The error.

event: error

The error event is emitted for every error that occurs during invalidation or for any rejection of promises that were pushed with the task's dispose event.

event: dispose

The dispose event is emitted when the entry task was invalidated.

runner.on('dispose', willContinue => ...)
  • willContinue <boolean> - true if the runner will re-run the entry task immediately. If dunner.dispose(..) was called beforehand, this will be false.

event: entry

The entry event is emitted when the entry task is run (or re-run after invalidation).

runner.on('entry', state => ...)
  • state <Promise> - The promise that is returned by .run()


Additional Api

Sometimes you might have to perform even more complex asynchronous operations. For this purpose, there is a collection of simple but useful classes that can be used to encapsulate specific actions inside tasks or the complete runner system:

Future

This class is an extended Promise that can be created at a time where it is unknown how the promise will resolve or reject.
To achieve this behaviour, a future can be resolved or rejected without an executor function passed to it's constructor. Anything else about a future is just like a promise. Also make sure that you dont pass parameters to the constructor, otherwise it will return a normal promise instead.

const {Future} = require('@drupe/tasks')

const future = new Future()

future.resolve([value])

Resolve the future with the given value.

future.resolve('foo')

future.reject([value])

Reject the future with the given value.

future.reject('bar')

future.pull(promise)

Forward the state of a promise to the future.

future.pull(promise)

// This is a shorthand for:
promise.then(future.resolve, future.reject)

WrappedPromise

The wrapped promise attaches to another promise and resolves or rejects only after .then or .catch was called. A WrappedPromise is also a Promise instance and can be used like one.

const {WrappedPromise} = require('@drupe/tasks')

const wrapper = new WrappedPromise(promise)
  • promise <Promise> - A promise to wrap.

Slot

A slot is a limited queue of async operations.
At most, one operation can be in progress and one other operation can be scheduled for execution.

const {Slot} = require('@drupe/tasks')

new Slot([shift])

Create a new slot.

new Slot()
new Slot(future => future.resolve())
  • shift <function> - A function that resolves or rejects the futures of scheduled tasks if they are pushed out of the queue by another task. By default, non-executed tasks will resolve to undefined.

slot.run(fn, ...args)

Run or schedule an async operation with the following behaviour:

  • If another operation is running:
    • If another operation is already scheduled as the next, the next operation's future is passed to the shift function which resolves or rejects it.
    • The operation is scheduled to run after the current one.
  • If no operation is running, the specified function is called with the specified arguments.
const promise = slot.run(someFunction, 'foo', 42)
  • fn <function> - The function to run. Note that this function must return a promise.
  • args <...any> - The arguments to pass to the function.
  • returns <Promise>
    • The promise returned by the function call if no other operation was running.
    • The future that resolves or rejects according to the operation if it was scheduled to run in the future.

slot.isFree

Check if the slot is currently running any operation.

slot.isFree // -> true | false

slot.free

Get a future that resolves when the slot is free and no other operation is scheduled anymore.

slot.free.then(() => {
    slot.isFree // -> true
})

If you find any bugs or have a feature request, please open an issue on github!

The npm package download data comes from npm's download counts api and package details come from npms.io.