hjs-future
Version:
Future classes of Hubrijs framework
1,918 lines (1,497 loc) • 48.3 kB
Markdown
# HJS-FUTURE
> Future classes for the Hubrisjs javascript framework.
Some future classes: Executors, Callable, Future, BlockingQueue, LinkedBlockingQueue, FutureTask, QueueingFuture, CompletionService, ExecutorCompletionService, AbstractExecutorService, AsyncTask and QueuedWork.
## Npm lib installation
Node:
```sh
npm install hjs-future --save
```
## Babel installation
Node:
```sh
npm install --save-dev babel-cli babel-plugin-transform-runtime babel-polyfill babel-preset-env babel-runtime
```
## Webpack installation for web usage
Node:
```sh
npm install --save-dev babel-loader webpack
```
## Table of Contents
* [Usage of executors](#usage-of-executors)
+ [Executor](#create-an-executor)
+ [Execute an anonymous runnable](#execute-an-anonymous-runnable)
+ [Execute a runnable](#execute-a-runnable)
+ [Execution with parameters](#execution-with-parameters)
+ [Custom executors](#custom-parameters)
+ [Serial executor from capacity](#serial-executor-from-capacity)
+ [Serial executor from queue](#serial-executor-from-queue)
+ [Execute serial runnables](#execute-runnables)
+ [Async serial runnables execution](#async-runnables-execution)
+ [Active runnable on serial](#active-runnable-on-serial)
+ [Blocking executor from capacity](#blocking-executor-from-capacity)
+ [Blocking executor from queue](#blocking-executor-from-queue)
+ [Blocking runnables](#blocking-runnables)
+ [Blocking async runnables](#blocking-async-runnables)
+ [Active blocking runnable](#active-blocking-runnable)
+ [Parallel executor](#parallel-executor)
+ [Parallel runnables](#parallel-runnables)
+ [Prefilled parallel runnables](#prefilled-parallel-runnables)
+ [Parallel runnable promise](#parallel-runnable-promise)
+ [Parallel promise result](#parallel-promise-result)
+ [Active parallel runnable](#active-parallel-runnable)
+ [Callable executor factory](#callable-executor-factory)
+ [Single executor factory](#single-executor-factory)
+ [Serial executor factory](#serial-executor-factory)
+ [Blocking executor factory](#blocking-executor-factory)
+ [Parallel executor factory](#parallel-executor-factory)
+ [Task executor factory](#task-executor-factory)
+ [Front task executor factory](#front-task-executor-factory)
+ [Timed task executor factory](#timed-task-executor-factory)
+ [Delayed task executor factory](#delayed-task-executor-factory)
* [Usage of futures and callables](#usage-of-futures-and-callables)
+ [Callable](#callable)
+ [Future from callable](#future-from-callable)
+ [Future from runnable](#future-from-runnable)
+ [Future callable task](#future-callable-task)
+ [Future runnable task](#future-runnable-task)
+ [Cancel a callable task](#cancel-a-callable-task)
+ [Cancel a runnable task](#cancel-a-runnable-task)
+ [Cancel a future task](#cancel-a-future-task)
* [Usage of blocking queue](#usage-of-blocking-queue)
+ [Linked blocking queue](#linked-blocking-queue)
+ [Add](#add)
+ [AddAll](#addAll)
+ [Clear](#clear)
+ [Contains](#contains)
+ [Drain](#drain)
+ [Element](#element)
+ [Offer](#offer)
+ [Peek](#peek)
+ [Timeout Offer](#timeout-offer)
+ [Poll](#poll)
+ [Timeout poll](#timeout-poll)
+ [Put](#put)
+ [Remaining capacity](#remainingCapacity)
+ [Remove](#remove)
+ [Size](#size)
+ [Take](#take)
### Usage of executors
**Executor**'s are objects that executes submitted tasks. They are interfaces that provides a way of decoupling task submission from the mechanics of how each task will be run.
However, the **Executor**'s does not strictly require that execution be asynchronous. In the simplest case, an executor can run the submitted task immediately.
**SerialExecutor**'s auto execute submitted task
**BlockingExecutor**'s execute submitted task on demand
**ParallelExecutor**'s execute submitted task only when the queue is full
**Executors** is a factory and utility methods for **Executor** classes defined in this module.
**ExecutorService**'s are **Executor**'s implementation that provides methods to manage termination and methods that can produce a **Future** for tracking progress of one or more asynchronous tasks.
###### Executor
```javascript
import {Executor} from 'hjs-future';
// basic abstract executor
const E = new Executor();
```
###### Execute an anonymous runnable
```javascript
import {Executor} from 'hjs-future';
const R = {
run(...params) {
console.log("executed");
return null;
}
};
const E = new Executor();
E.execute(R);
```
###### Execute a runnable
```javascript
import {Runnable} from 'hjs-message';
import {Executor} from 'hjs-future';
const R = new Runnable({
run(...params) {
console.log("executed");
return null;
}
});
const E = new Executor();
E.execute(R);
```
###### Execution with parameters
```javascript
import {Runnable} from 'hjs-message';
import {Executor} from 'hjs-future';
const R = new Runnable({
run(...params) {
let opt = params[0];
console.log("executed");
if (opt) {
return opt.data;
}
return null;
}
});
const E = new Executor();
let data = E.execute(R, { data: "ok"});
console.log(data);
```
###### Implements a custom runnable
```javascript
import {Runnable} from 'hjs-message';
import {Executor} from 'hjs-future';
// Naive promise runnable implementation
const R = new Runnable({
run(...params) {
let opt = params[0];
return new Promise((resolve, reject) => {
if (opt && opt.data === "data to compute") {
resolve("ok");
} else {
reject("ko");
}
});
}
});
const E = new Executor();
// this return a promise that is resolved
E.execute(R, { data: "data to compute" })
.then((result) => {
console.log(result);
})
.catch((e) => {
console.log(e);
});
```
###### Custom executors
```javascript
import {Executor} from 'hjs-future';
const R = {
run(...params) {
let result = params[0] + " world";
return result;
}
};
// Naive promise executor implementation
const E = new Executor({
execute(r=null,...params) {
return new Promise((resolve, reject) => {
if (r) {
setTimeout(() => {
// make computation
resolve(r.run.apply(r, params));
}, 1000);
} else {
reject(new Error("NullPointerException"));
}
});
}
});
E.execute(R, "Hello")
.then((result) => {
console.log(result === "Hello world");
})
.catch((e) => {
console.log(e);
});
```
###### Serial executor from capacity
```javascript
import {SerialExecutor} from 'hjs-future';
const capacity = 100;
const S = new SerialExecutor({
capacity /*max runnable in the queue (default to 10)*/
});
```
###### Serial executor from queue
```javascript
import {RingBuffer} from 'hjs-collection';
import {SerialExecutor} from 'hjs-future';
const CAPACITY = 10;
const queue = new RingBuffer(CAPACITY);
const S = new SerialExecutor({
queue /*AbstractQueue implementation (default to Queue)*/
});
```
###### Execute serial runnables
```javascript
import {SerialExecutor} from 'hjs-future';
const SE = new SerialExecutor({ capacity });
for (let i=0; i<capacity; i++) {
// enqueue runnables (this impementation of the execute method return nothing)
SE.execute({
run(...params) {
let index = params[0];
// sync block code never block the queue
console.log("executed at index " + index);
return index;
}
}, i);
};
// start queue execution
SE.scheduleNext();
```
###### Async serial runnables execution
```javascript
import {SerialExecutor} from 'hjs-future';
const capacity = 10;
const SE = new SerialExecutor({ capacity });
// runnables creator
const createRunnable = () => {
return {
run(...params) {
// for simplicity put computation on promise
return new Promise((resolve, reject) => {
let index = params[0];
if (index < capacity) {
if (index === 4 || index === 7) {
// async block code can block the queue
setTimeout(() => {
resolve("async executed at index " + index);
}, index === 4 ? 2000 : 500);
} else {
// sync block code never block the queue
resolve("executed at index " + index);
}
}
});
}
};
};
for (let i=0; i<capacity; i++) {
// enqueue runnables
SE.execute(createRunnable(), i);
};
// start queue execution and get all results as a promise
SE.getPromiseResults()
.then((results) => {
results.forEach((value) => {
// ordered results
console.log(value);
});
})
.catch((e) => {
// no error in this example
});
```
###### Active runnable on serial
```javascript
import {SerialExecutor} from 'hjs-future';
const capacity = 10;
const promises = new Array(capacity);
const SE = new SerialExecutor({ capacity });
for (let i = 0; i < capacity; i++) {
let p = new Promise((resole, reject) => {
let first = SE.size() === 0;
// enqueue a runnable task
SE.execute({
run(...params) {
let index = params[0];
console.log("executed at index " + index);
return this.index = index;
}
}, i);
// getting the active runnable
let active = SE.active();
// edge case start the queue
if (first) {
SE.scheduleNext();
}
resole(active);
});
promises[i] = p;
}
Promise.all(promises)
.then((actives) => {
let results = SE.getResults();
// getting all runnables that was activated
actives.forEach((active) => {
// getting the task runnable that was submit
let task = active.task;
// only for validation purpose
if (results[active.task.index] === active.task.index) {
console.log("runnable complete at index " + active.task.index);
}
});
});
```
###### Blocking executor from capacity
```javascript
import {BlockingExecutor} from 'hjs-future';
const capacity = 10;
const S = new BlockingExecutor({
capacity /*max runnable in the queue (default to 10)*/
});
```
###### Blocking executor from queue
```javascript
import {RingBuffer} from 'hjs-collection';
import {BlockingExecutor} from 'hjs-future';
const CAPACITY = 10;
const queue = new RingBuffer(CAPACITY);
const S = new BlockingExecutor({
queue /*AbstractQueue implementation (default to Queue)*/
});
```
###### Blocking runnables
```javascript
import {BlockingExecutor} from 'hjs-future';
const capacity = 10;
const BE = new BlockingExecutor({ capacity });
for (let i=0; i<capacity; i++) {
// enqueue runnables
BE.execute({
run(...params) {
let index = params[0];
console.log("executed at index " + index);
// don't forget to execute next task
BE.scheduleNext();
return index;
}
}, i);
};
// start queue execution and get all results
// Be care full result are in reversed order here, because tasks are enqueued from sub tasks
// last task become first.
console.log(BE.scheduleNext().getResults());
```
###### Blocking async runnables
```javascript
import {RingBuffer} from 'hjs-collection';
import {BlockingExecutor} from 'hjs-future';
const capacity = 10;
const queue = new RingBuffer(capacity);
const BE = new BlockingExecutor({ queue });
const createRunnable = (executor) => {
return {
run(...params) {
// don't forget to execute next task
executor.scheduleNext();
return new Promise((resolve, reject) => {
let idx = params[0];
if (idx <= capacity) {
if (idx === 4 || idx === 7) {
setTimeout(() => {
resolve("async executed at index " + idx);
}, idx === 4 ? 2000 : 500);
} else {
resolve("executed at index " + idx);
}
}
});
}
};
};
for (let i=0; i < capacity; i++) {
BE.execute(createRunnable(BE), i);
}
// start queue execution and get all results as a promise
BE.getPromiseResults()
.then((results) => {
console.log(results);
results.forEach((result) => {
console.log(result);
});
})
.catch((e) => {
console.log(e);
});
```
###### Active blocking runnable
```javascript
import {BlockingExecutor} from 'hjs-future';
const capacity = 10;
const BE = new BlockingExecutor({ capacity });
new Promise((resole, reject) => {
let actives = [];
// enqueue a runnable task
for (let i = 0; i < capacity; i++) {
BE.execute({
index: i,
run(...params) {
console.log("executed at index " + this.index);
if (BE.isEmpty()) {
resole(actives);
}
return "[" + this.index + "]";
}
}, i);
}
let active = null;
while(BE.isActive() && (active = BE.active())) {
actives.push(active);
// don't forget to execute next task
BE.scheduleNext();
}
}).then((actives) => {
// results are in natural orders
console.log(BE.getResults());
// getting all runnables that was activated
actives.forEach((active) => {
// getting the task runnable that was submit
let task = active.task;
console.log("runnable complete at index " + active.task.index);
});
});
```
###### Parallel executor
```javascript
import {LinkedList} from 'hjs-collection';
import {ParallelExecutor} from 'hjs-future';
const capacity = 10;
const queue = new LinkedList();
const PE = new ParallelExecutor({
capacity /*max runnable in the queue (default to 10)*/,
queue /*AbstractQueue implementation (default to LinkedList)*/
});
```
###### Parallel runnables
```javascript
import {ParallelExecutor} from 'hjs-future';
const capacity = 5;
const PE = new ParallelExecutor({ capacity });
for (let i=0; i<capacity - 1; i++) {
PE.execute({
run(...params) {
console.log("executed at index " + params[0]);
return params[0];
}
}, i);
}
// queue not full
let isFull = PE.isFull();
if (!isFull) {
// when we add the last element the queue is executed
PE.execute({
run(...params) {
console.log("executed at index " + params[0]);
return params[0];
}
}, 4);
}
```
###### Prefilled parallel runnables
```javascript
import {LinkedList} from 'hjs-collection';
import {ParallelExecutor} from 'hjs-future';
const capacity = 5;
const tasks = new Array(capacity);
for (let i=0; i<capacity; i++) {
tasks[i] = {
run(...params) {
console.log("executed at index " + params[0]);
return params[0];
}
};
}
// fill the queue
const queue = new LinkedList(tasks);
// later in the code
const PE = new ParallelExecutor({ capacity, queue });
// here queue is full
PE.executeAll();
```
###### Parallel runnable promise
```javascript
import {Queue} from 'hjs-collection';
import {ParallelExecutor,RunnablePromise} from 'hjs-future';
const lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua".split(" ");
const capacity = lorem.length;
const PE = new ParallelExecutor({capacity});
const queue = new Queue(capacity);
lorem.map((word, index) => {
return new RunnablePromise({
handlePromise(result) {
let complete = index === capacity - 1;
if (queue.offer(result) && complete) {
let occurence;
while ((occurence = queue.poll())) {
console.log(occurence);
}
}
},
process(resolve, reject) {
setTimeout(() => {
resolve(word + index);
}, 250);
}
});
})
.forEach((runnable) => {
PE.execute(runnable);
});
```
###### Parallel promise result
```javascript
import {ParallelExecutor} from 'hjs-future';
const capacity = 10;
const PE = new ParallelExecutor({ capacity });
const createRunnable = (executor) => {
return {
run(...params) {
return new Promise((resolve, reject) => {
let idx = params[0];
if (idx <= capacity) {
if (idx === 4 || idx === 7) {
setTimeout(() => {
resolve("async executed at index " + idx);
}, idx === 4 ? 2000 : 500);
} else {
resolve("executed at index " + idx);
}
}
});
}
};
};
for (let i=0; i < capacity; i++) {
PE.execute(createRunnable(PE), i);
}
// start queue execution and get all results as a promise
PE.getPromiseResults()
.then((results) => {
console.log(results);
results.forEach((result) => {
console.log(result);
});
})
.catch((e) => {
console.log(e);
});
```
###### Active parallel runnable
```javascript
import {ParallelExecutor} from 'hjs-future';
const capacity = 5;
const PE = new ParallelExecutor({capacity});
const actives = [];
const runnables = [];
for (let i = 0; i < capacity; i++) {
runnables[i] = {
index: i,
resolve: null,
reject: null,
run() {
if (this.resolve) {
this.resolve(this.index);
}
}
};
}
Promise.all(runnables.map((runnable) => {
return new Promise((resolve, reject) => {
runnable.resolve = resolve;
runnable.reject = reject;
PE.execute(runnable);
actives.push(PE.active());
});
}))
.then((indexes) => {
indexes.forEach((value) => {
let active = actives[value];
let task = active.task;
console.log(task.index);
});
});
```
###### Callable executor factory
```javascript
import {Executors} from 'hjs-future';
// A runnable
let runnable = {
run() {
console.log("A task");
}
};
// An optionnal result
let result = "A default task";
// Create a callable instance
let callable = Executors.callable(runnable, result);
```
###### Single executor factory
```javascript
import {Executors} from 'hjs-future';
let PE = Executors.newSingleExecutor({
execute(r=null,...params) {
if (r) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// make computation
resolve(r.run.apply(r, params));
}, 1000);
});
}
return null;
}
});
```
###### Serial executor factory
```javascript
import {Queue} from 'hjs-collection';
import {Executors} from 'hjs-future';
let SE1 = Executors.newSerialExecutor({
capacity: 10
});
let SE2 = Executors.newSerialExecutor({
queue: new Queue(10)
});
```
###### Blocking executor factory
```javascript
import {Queue} from 'hjs-collection';
import {Executors} from 'hjs-future';
let BE1 = Executors.newBlockingExecutor({
capacity: 10
});
let BE2 = Executors.newBlockingExecutor({
queue: new Queue(10)
});
```
###### Parallel executor factory
```javascript
import {LinkedList} from 'hjs-collection';
import {Executors} from 'hjs-future';
let PE = Executors.newParallelExecutor({
capacity: 10,
queue: new LinkedList()
});
```
###### Task executor factory
```javascript
import {MessageHandler} from 'hjs-message';
import {BlockingExecutor, Executors} from "hjs-future";
const capacity = 10;
// Create an executor that post tasks on message handler
const PE = Executors.postExecutor(
MessageHandler.create(),
new BlockingExecutor({ capacity })/*any type of executors accepted if not specified a single executor is used*/
);
// execute runnables on the same executor
for (let i=0; i<capacity; i++) {
PE.execute({
run(handler, token=null) {
console.log("executed at index " + token);
// mark message has handled
return true;
}
}, i/* optional token*/);
}
```
###### Front task executor factory
```javascript
import {MessageHandler} from 'hjs-message';
import {SerialExecutor, Executors} from "hjs-future";
const capacity = 10;
// Create an executor that always post at front of the queue
const PE = Executors.postExecutorAtFrontOfQueue(
MessageHandler.create(),
new SerialExecutor({ capacity })/*any type of executors accepted if not specified a single executor is used*/
);
for (let i=0; i<capacity; i++) {
PE.execute({
run(handler, token=null) {
console.log("executed " + token);
return true;
}
}, i/* optional token*/);
}
```
###### Timed task executor factory
```javascript
import {MessageHandler} from 'hjs-message';
import {Executor, Executors} from "hjs-future";
const capacity = 10;
// Post at time executor
const PE = Executors.postExecutorAtTime(
MessageHandler.create(),
new Executor() /*Lite weight executor that haven't internal queue if not specified a single executor is used*/
);
for (let i=0; i<capacity; i++) {
let START_TIME = Date.now();
let WAIT_TIME = 200 * (i + 1);
let UPTIME_MILLIS = START_TIME + WAIT_TIME;
PE.execute({
run(handler, token=null) {
console.log("executed at index " + token);
let now = Date.now();
let when = START_TIME;
let ellapsed = now - when;
let diff = UPTIME_MILLIS - when;
console.log("now: " + now + ", when: " + when + ", ellapsed: " + ellapsed + ", diff: " + diff);
return true;
}
}, UPTIME_MILLIS, i);
}
```
###### Delayed task executor factory
```javascript
import {MessageHandler} from 'hjs-message';
import {ParallelExecutor, Executors} from "hjs-future";
const capacity = 10;
// Post at delayed executor
const PE = Executors.postExecutorDelayed(
MessageHandler.create(),
new ParallelExecutor({ capacity })/*any type of executors accepted if not specified a single executor is used*/
);
for (let i=0; i<capacity; i++) {
let start = Date.now();
let delay = 200 * (i + 1);
PE.execute({
run(handler, token=null) {
console.log("executed at index " + token);
let now = Date.now();
let when = start;
let ellapsed = now - when;
console.log("now: " + now + ", when: " + when + ", ellapsed: " + ellapsed + ", delay " + delay);
return true;
}
}, delay, i);
}
```
### Usage of futures and callables
**Callable**'s are task that returns a result and may throw an exception. Implementors define a single method called
**compute**.
**Future**'s represents the result of an asynchronous computation. Methods are provided to check if the computation is
complete, to wait for its completion, and to retrieve the result of the computation. The result can only be retrieved
using method **get** when the computation has completed, blocking if necessary until it is ready. Cancellation is
performed by the **cancel** method. Additional methods are provided to determine if the task completed normally or was
cancelled. Once a computation has completed, the computation cannot be cancelled. The framework give you a future
implementation named **FutureTask**.
###### Callable
```javascript
import {time} from "hjs-core";
import {Callable} from "hjs-future";
// basic example that manually start a callable
const C = new Callable({
/* compute is the only accepted arguments */
compute() {
let P = new Promise((resolve, reject) => {
// sleep 2 seconds
time.SECONDS.sleep(
/* make computation*/
() => { resolve("ok"); },
/*wait time in seconds in this example*/
2,
/*Optional exception handler for this time unit*/
new time.UncaughtExceptionHandler({
/*catch any exception (in this example can't appen)*/
uncaughtException(e) {
reject(e);
}
})
);
})
.then((result) => {
// signal the result
this.signal(result);
})
.catch((e) => {
// signal this error
onComplete(e);
});
}
});
// listen for computation
C.on(Callable.COMPUTE, (result) => {
C.removeAllListeners(Callable.COMPUTE);
console.log(result);
});
// start computation
C.call();
```
###### Future from callable
```javascript
import {Callable,FutureTask} from "hjs-future";
/* create a future task with a Callable instance */
const F1 = new FutureTask({
/*An associated callable instance */
callable: new Callable({
compute() {
}
}),
/* An optional callback */
done(result=null) {
}
});
/* create a future task with an anonymous callable */
const F2 = new FutureTask({
/*An associated anonymous callable */
callable: {
compute() {
}
}
});
/* create a future task with an anonymous computation */
const F23 = new FutureTask({
/*An associated anonymous computation */
callable: () => {
}
});
```
###### Future from runnable
```javascript
import {Runnable} from "hjs-message";
import {FutureTask} from "hjs-future";
/* create a future task with a Runnable instance */
const F1 = new FutureTask({
/*An optional default result*/
result: "my default data",
/*An associated runnable instance */
runnable: new Runnable({
run(callable) {
}
}),
/* An optional callback */
done(result=null) {
}
});
/* create a future task with an anonymous runnable */
const F2 = new FutureTask({
/*An associated anonymous runnable */
runnable: {
run(callable) {
}
}
});
/* create a future task with an anonymous computation */
const F23 = new FutureTask({
/*An associated anonymous computation */
runnable: (callable) => {
}
});
```
###### Future callable task
```javascript
import {time} from "hjs-core";
import {FutureTask} from "hjs-future";
/* create a future task */
const F = new FutureTask({
/*An associated callable*/
callable: {
compute() {
time.MILLISECONDS.sleep(() => {
// signal the result
this.signal("ok");
}, 100);
}
}
});
/*Execute this task before a timeout of 1 seconds*/
F.get({
/*The timeout in seconds*/
timeout: 1,
/*The time unit is seconds*/
unit: time.SECONDS,
/* The completion callback not specified in the constructor but here*/
done(result) {
/*If a timeout occur this result is an instanceof error*/
console.log(result);
}
});
//somewhere in the code
setTimeout(() => {
let x = F.get();
// retrieve the callable result
console.log(x === "ok");
}, 3000);
```
###### Future runnable task
```javascript
import {time} from "hjs-core";
import {FutureTask} from "hjs-future";
const result = "TEST";
const F = new FutureTask({
/*Optional default result*/
result,
/*An associated runnable*/
runnable: (callable) => {
time.MILLISECONDS.sleep(() => {
// signal an empty result so the default one is used
callable.signal();
}, 100);
}
});
/*Execute this task before a timeout of 1 seconds*/
F.get({
/*The timeout in seconds*/
timeout: 1,
/*The time unit is seconds*/
unit: time.SECONDS,
/* The completion callback not specified in the constructor but here*/
done(result) {
/*If a timeout occur this result is an instanceof error*/
console.log(result);
}
});
setTimeout(() => {
let x = F.get();
// retrieve the runnable result
console.log(x === result);
}, 3000);
```
###### Cancel a callable task
```javascript
import {time} from "hjs-core";
import {FutureTask} from "hjs-future";
const F = new FutureTask({
callable: {
compute() {
time.MILLISECONDS.sleep(() => {
// cancel this task
this.cancel();
}, 100);
}
}
});
F.get({
timeout: 1,
unit: time.SECONDS,
done(result) {
console.log(result);
}
});
setTimeout(() => {
// this is an instanceof error
let x = F.get();
// this is false
console.log(x);
}, 2000);
```
###### Cancel a runnable task
```javascript
import {time} from "hjs-core";
import {FutureTask} from "hjs-future";
const result = "TEST";
const F = new FutureTask({
result,
runnable: (callable) => {
time.MILLISECONDS.sleep(() => {
// cancel this task
callable.cancel();
}, 100);
}
});
F.get({
timeout: 1,
unit: time.SECONDS,
done(result) {
console.log(result);
}
});
setTimeout(() => {
// this is an instanceof error
let x = F.get();
// this is false
console.log(x === result);
}, 2000);
```
###### Cancel a future task
```javascript
import {time} from "hjs-core";
import {FutureTask} from "hjs-future";
/* create a future task */
const F = new FutureTask({
callable: {
compute() {
time.SECONDS.sleep(() => {
// we can test the cancellation condition but it's a wast of time
// when cancelled a future never receive signal
if (!F.isCancelled()) {
// signal the result
this.signal("ok");
}
}, 1);
}
}
});
F.get({
timeout: 2,
unit: time.SECONDS,
done(result) {
/*The cancellation occur this result is an instanceof error or never called if the get is not called*/
console.log(result);
}
});
//somewhere in the code
setTimeout(() => {
// cancel the task
F.cancel();
// the task is cancelled
if (F.isCancelled()) {
// if we call get the done callback is invoked with a cancellation error otherwise nothing is notified
F.get();
}
}, 500);
```
### Usage of blocking queue
**BlockingQueue**'s are **AbstractQueue** that additionally supports operations that wait for the queue to become
non-empty when retrieving an element, and wait for space to become available in the queue when storing an element.
**BlockingQueue** methods come in four forms, with different ways of handling operations that cannot be satisfied
immediately, but may be satisfied at some point in the future.
**LinkedBlockingQueue** is a **BlockingQueue** implementation based on linked nodes.
This queue orders elements FIFO (first-in-first-out).
The head of the queue is that element that has been on the queue the longest time.
The tail of the queue is that element that has been on the queue the shortest time. New elements are inserted at the
tail of the queue, and the queue retrieval operations obtain elements at the head of the queue. Linked queues typically
have higher throughput than array-based queues but less predictable performance in most concurrent applications.
The optional capacity bound constructor argument serves as a way to prevent excessive queue expansion. The capacity,
if unspecified, is equal to **Number.MAX_VALUE**. Linked nodes are dynamically created upon each insertion unless this
would bring the queue above capacity.
###### Linked blocking queue
```javascript
import {LinkedBlockingQueue} from "hjs-future";
// bound to 5 elements
const capacity = 5;
// create the queue
let LBQ = new LinkedBlockingQueue({ capacity });
```
###### Add
```javascript
import {LinkedBlockingQueue} from "hjs-future";
const capacity = 1;
let LBQ = new LinkedBlockingQueue({ capacity });
// add an element
let added = LBQ.add({ data: "Added"});
// true if the element was added
console.log(added);
```
###### AddAll
```javascript
import {LinkedBlockingQueue} from "hjs-future";
const collection = [{ data: "Added0"},{ data: "Added1"},{ data: "Added2"},{ data: "Added3"}];
const capacity = collection.length;
let LBQ = new LinkedBlockingQueue({ capacity });
// add all elements
let added = LBQ.addAll(collection);
// true if the queue was modified
console.log(added);
```
###### Clear
```javascript
import {LinkedBlockingQueue} from "hjs-future";
const collection = [{ data: "Added0"},{ data: "Added1"},{ data: "Added2"},{ data: "Added3"}];
const capacity = collection.length;
let LBQ = new LinkedBlockingQueue({ capacity });
if (LBQ.addAll(collection)) {
// clear the queue
LBQ.clear();
}
```
###### Contains
```javascript
import {LinkedBlockingQueue} from "hjs-future";
const collection = [{ data: "Added0"},{ data: "Added1"},{ data: "Added2"},{ data: "Added3"}];
const capacity = collection.length;
let LBQ = new LinkedBlockingQueue({ capacity });
// true if the queue was modified && contains the first element
console.log(LBQ.addAll(collection) && LBQ.contains(collection[0]));
```
###### Drain
```javascript
import {LinkedBlockingQueue} from "hjs-future";
const collection = [{ data: "Added0"},{ data: "Added1"},{ data: "Added2"},{ data: "Added3"}];
const capacity = collection.length;
let LBQ = new LinkedBlockingQueue({ capacity });
// true if the queue was modified && contains the first element
console.log(LBQ.addAll(collection) && LBQ.drainTo(new Array(capacity)));
```
###### Element
```javascript
import {LinkedBlockingQueue} from "hjs-future";
const collection = [{ data: "Added0"},{ data: "Added1"},{ data: "Added2"},{ data: "Added3"}];
const capacity = collection.length;
let LBQ = new LinkedBlockingQueue({ capacity });
// true if the queue was modified && contains the first element
console.log(LBQ.addAll(collection) && LBQ.element());
```
###### Offer
```javascript
import {time} from "js-core";
import {LinkedBlockingQueue} from "hjs-future";
const capacity = 4;
let LBQ = new LinkedBlockingQueue({ capacity });
console.log("=========================== OFFER ");
for (let i=0; i<capacity; i++) {
// offer an element
let bool = LBQ.offer(i, {
/* optional timeout (default to 0) */
timeout: 0,
/* optional time unit (default to MILLIS) */
unit: time.MILLIS,
/* optional callabck */
callback(node) {
// on POLL event
if (node instanceof Error) {
console.log("--> failed offer to " + i);
console.error(node);
} else {
console.log("--> offer to " + i);
}
}
});
// always true/false
console.log(bool);
}
// here the queue is full
console.log(LBQ.remainingCapacity() === 0);
```
###### Timeout Offer
```javascript
import {time} from 'js-core';
import {LinkedBlockingQueue} from "hjs-future";
const capacity = 4;
let LBQ = new LinkedBlockingQueue({ capacity });
// overflow 50%
const overflow = Math.floor(capacity / 2);
// buffer size
const bufferOverflow = capacity + overflow;
// timeout
let timeout = 1000;
console.log("=========================== OFFER ");
for (let i=0; i<bufferOverflow; i++) {
// offer an element
let bool = LBQ.offer(i, {
timeout: i > capacity ? timeout : 0,
callback(node) {
// on a POLL event
if (node instanceof Error) {
console.log("--> failed offer to " + i);
console.error(node);
} else {
console.log("--> offer to " + i);
}
}
});
// the result of the operation (true/false)
console.log(bool);
}
// here the queue is full
console.log(LBQ.remainingCapacity() === 0);
```
###### Peek
```javascript
import {LinkedBlockingQueue} from "hjs-future";
const data = [{ data: "Added0"},{ data: "Added1"},{ data: "Added2"},{ data: "Added3"}];
const LBQ = new LinkedBlockingQueue({ data });
// the first element
console.log(LBQ.peek().data === "Added0");
```
###### Poll
```javascript
import {time} from "js-core";
import {LinkedBlockingQueue} from "hjs-future";
const capacity = 4;
let LBQ = new LinkedBlockingQueue({ capacity });
console.log("=========================== POLL ");
for (let i=0; i<capacity; i++) {
// poll an element
let element = LBQ.poll({
/* optional timeout (default to 0) */
timeout: 0,
/* optional time unit (default to MILLIS) */
unit: time.MILLISECONDS,
/* optional callback */
callback(element) {
// on OFFER event
if (element instanceof Error) {
console.log("--> failed poll from " + i);
console.error(element);
} else {
console.log("--> poll from " + i);
}
}
});
// null element are waiting async completion
console.log(element === null);
}
// the queue is empty
console.log(LBQ.remainingCapacity() === capacity);
```
###### Timeout poll
```javascript
import {LinkedBlockingQueue} from "hjs-future";
const capacity = 4;
let LBQ = new LinkedBlockingQueue({ capacity });
// overflow 50%
const overflow = Math.floor(capacity / 2);
// buffer size
const bufferOverflow = capacity + overflow;
// timeout
let timeout = 1000;
console.log("=========================== POLL ");
for (let i=0; i<bufferOverflow; i++) {
// poll an element
let element = LBQ.poll({
timeout: i > capacity ? timeout : 0,
callback(element) {
// on OFFER event
if (element instanceof Error) {
console.log("--> failed poll from " + i);
console.error(node);
} else {
console.log("--> poll from " + i);
}
}
});
// null element are waiting async completion
console.log(element === null);
}
// the queue is empty
console.log(LBQ.remainingCapacity() === capacity);
```
###### Put
```javascript
import {LinkedBlockingQueue} from "hjs-future";
const capacity = 4;
const LBQ = new LinkedBlockingQueue({ capacity });
let item = { data: "My secret" };
LBQ.put(item, (node=null) => {
// on POLL event
if (node instanceof Error) {
console.log("--> failed put");
console.error(node);
} else {
console.log("--> put node ");
console.log(node);
}
});
// the first element
console.log(LBQ.peek().data === "My secret");
```
###### Remaining capacity
```javascript
import {LinkedBlockingQueue} from "hjs-future";
const capacity = 4;
const LBQ = new LinkedBlockingQueue({ capacity });
let item = { data: "My secret" };
LBQ.put(item, (node=null) => {
// on POLL event
if (node instanceof Error) {
console.log("--> failed put");
console.error(node);
} else {
console.log("--> put node ");
console.log(node);
}
});
// the first element
console.log(LBQ.peek().remainingCapacity() === 3);
```
###### Remove
```javascript
import {LinkedBlockingQueue} from "hjs-future";
const capacity = 4;
const LBQ = new LinkedBlockingQueue({ capacity });
let item1 = { data: "My secret 1" };
LBQ.put(item1);
let item2 = { data: "My secret 2" };
LBQ.put(item2);
if (LBQ.remove(item2)) {
console.log(LBQ.peek().data === "My secret 1");
}
```
###### Size
```javascript
import {LinkedBlockingQueue} from "hjs-future";
const capacity = 4;
const LBQ = new LinkedBlockingQueue({ capacity });
let item = { data: "My secret" };
LBQ.put(item);
// the first element
console.log(LBQ.size() === 1);
```
###### Take
```javascript
import {LinkedBlockingQueue} from "hjs-future";
const capacity = 4;
const LBQ = new LinkedBlockingQueue({ capacity });
let item = { data: "My secret" };
LBQ.put(item);
// the first element
console.log(LBQ.size() === 1);
```
##### Offer and Poll operation (2 way data binding)
```javascript
import {LinkedBlockingQueue} from "hjs-future";
const capacity = 4;
const overflow = Math.floor(capacity / 2);
const bufferOverflow = capacity + overflow;
const threshold = Math.floor(bufferOverflow / 2);
const LBQ = new LinkedBlockingQueue({ capacity });
console.log("=========================== OFFER ");
for (let i=0; i<bufferOverflow; i++) {
// offer an element or wait until the next POLL event
let full = LBQ.offer(i, {
callback(element=null) {
if (element instanceof Error) {
console.log("--> failed offer to " + i);
console.error(err);
} else {
console.log("--> offer to " + i);
}
}
});
}
// wakeup operation
setTimeout(() => {
console.log("=========================== POLL ");
for (let i=0; i<threshold; i++) {
// poll an element or wait until the next OFFER event
let polled = LBQ.poll({
callback(node, err=null) {
if (err) {
console.log("--> failed poll from " + i);
console.error(err);
} else {
console.log("--> poll to " + i);
}
}
});
}
}, 500);
```
##### Offer and Poll with delay operation (2 way data binding)
```javascript
import {LinkedBlockingQueue} from "hjs-future";
const capacity = 4;
const overflow = Math.floor(capacity / 2);
const bufferOverflow = capacity + overflow;
const threshold = Math.floor(bufferOverflow / 2);
const timeout = 1000;
const LBQ = new LinkedBlockingQueue({ capacity });
console.log("=========================== OFFER ");
for (let i=0; i<bufferOverflow; i++) {
// offer an element or wait until the next POLL event
let full = LBQ.offer(i, {
timeout: i > capacity ? timeout : 0,
callback(element=null) {
if (element instanceof Error) {
console.log("--> failed offer to " + i);
console.error(err);
} else {
console.log("--> offer to " + i);
}
}
});
console.log(full);
}
// wakeup before poll operation
setTimeout(() => {
console.log("=========================== POLL ");
for (let i=0; i<threshold; i++) {
// poll an element or wait until the next OFFER event
let polled = LBQ.poll({
timeout: i > capacity ? timeout : 0,
callback(node, err=null) {
if (err) {
console.log("--> failed poll from " + i);
console.error(err);
} else {
console.log("--> poll to " + i);
}
}
});
}
}, 500);
```
## Usage of executor services
**AbstractExecutorService**'s provides an abstract implementation of **ExecutorService**'s execution methods. This class
implements the **submit**, **invokeAny** and **invokeAll** methods using a **RunnableFuture** returned by
**newTaskFor**, which defaults to the **FutureTask** class provided in this module.
**ExecutorCompletionService** uses a supplied **Executor** to execute tasks. This class arranges that submitted tasks
are, upon completion, placed on a queue accessible using **take**. The class is lightweight enough to be suitable for
transient use when processing groups of tasks.
**PoolExecutorService** is an **ExecutorService** that executes each submitted task using one of possibly several pooled
futures.
Executor service pools address two different problems:
1. They usually provide improved performance when executing large numbers of
asynchronous tasks, due to reduced per-task invocation overhead.
2. They provide a means of bounding and managing the
resources, including futures, consumed when executing a collection of tasks.
Each **PoolExecutorService** also maintains some basic statistics, such as the number of completed tasks.
To be useful across a wide range of contexts, this class provides many adjustable parameters and extensibility hooks.
###### Create an abstract executor service
```javascript
import {AbstractExecutorService} from "hjs-future";
const AES = new AbstractExecutorService();
```
###### Submit tasks on an abstract executor service
```javascript
import {time} from "hjs-core";
import {Queue} from "hjs-collection";
import {AbstractExecutorService} from "hjs-future";
const capacity = 10;
const Q = new Queue(capacity);
const AES = new AbstractExecutorService();
for (let i=0; i<capacity; i++) {
// submit a callable and return a future for later execution
let future = AES.submit({
callable: {
compute() {
const sleep = Math.floor(Math.random() * 5) + 1;
time.SECONDS.sleep(() => {
this.signal(i + " callable computed randomly at " + sleep + "s");
}, sleep);
}
}
});
// add in the queue for later execution
if (!Q.offer(future)) {
console.log("Queue full");
}
}
let future = null;
// later in the code
while((future = Q.poll())) {
// execute the future task
future.get({
timeout: 4,
unit: time.SECONDS,
done(result) {
console.log("[done: " + this.isDone() + ", cancelled: " + this.isCancelled() + "]");
console.log(result);
}
});
}
```
###### Invoke all tasks on an abstract executor service
```javascript
import {time} from "hjs-core";
import {AbstractExecutorService} from "hjs-future";
const capacity = 10;
// list of callables
const callables = new Array(capacity);
// create an abstract executor service implementation
const AES = new AbstractExecutorService();
for (let i=0; i<capacity; i++) {
// fill the list with callables
callables[i] = {
compute() {
const sleep = Math.floor(Math.random() * 5) + 1;
// some tasks can't termine the work before the executor service timeout
time.SECONDS.sleep(() => {
this.signal("At index " + i + " a callable is computed randomly after " + sleep + "s");
}, sleep);
}
};
}
// Invoke all the task waiting either if all tasks are terminated before the timeout or not
AES.invokeAll({
/* The list of callables tasks to submit */
tasks: callables,
/* The maximum time to wait before consider that a task is in timeout here in seconds */
timeout: 3,
/* The time unit here in seconds */
unit: time.SECONDS,
/* The completion callback that return all submited tasks that are complete or not */
onInvoke(futures) {
// Grab the results all elements are futures
futures.forEach((future) => {
console.log("***********");
// If a task is out of range its not done and mark as cancelled
console.log("[done: " + future.isDone() + ", cancelled: " + future.isCancelled() + "]");
// If a task enter timeout is result is an instanceof error
console.log(future.get());
});
}
});
```
## Contacts
[Aime - abiendo@gmail.com](abiendo@gmail.com)
Distributed under the MIT license. See [``LICENSE``](./LICENSE.md) for more information.