@drupe/tasks v0.6.0
@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 aWrappedPromise
(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 thetask.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 isfalse
. - 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. Ifdunner.dispose(..)
was called beforehand, this will befalse
.
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
})