experimentng with task manager concept + refactoring...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2020-11-24 05:48:54 +03:00
parent ea6fbc1ff5
commit ee91cd2601
5 changed files with 264 additions and 90 deletions

View File

@ -13,7 +13,6 @@ var object = require('ig-object')
/*********************************************************************/
// XXX should this be aborted on reject???
var IterablePromise =
module.IterablePromise =
object.Constructor('IterablePromise', Promise, {
@ -95,10 +94,10 @@ object.Constructor('IterablePromise', Promise, {
//
// NOTE: .catch(..) and .finally(..) are implemented through .then(..)
// so we do not need to overload those...
then: function(onfulfilled, onrejected){
then: function (onfulfilled, onrejected){
return new Promise(
function(resolve, reject){
object.parentCall(IterablePromise.prototype.then, this,
Promise.prototype.then.call(this,
// NOTE: resolve(..) / reject(..) return undefined so
// we can't pass them directly here...
function(res){
@ -108,7 +107,7 @@ object.Constructor('IterablePromise', Promise, {
reject(res)
return res }) }.bind(this))
.then(...arguments) },
//
// Promise.iter([ .. ])
@ -145,8 +144,9 @@ object.Constructor('IterablePromise', Promise, {
var promise
// instance...
var obj = Reflect.construct(IterablePromise.__proto__, [
function(resolve, reject){
var obj = Reflect.construct(
IterablePromise.__proto__,
[function(resolve, reject){
// NOTE: this is here for Promise compatibilty...
if(typeof(list) == 'function'){
return list.call(this, ...arguments) }
@ -202,48 +202,112 @@ object.Constructor('IterablePromise', Promise, {
//---------------------------------------------------------------------
var InteractivePromise =
module.InteractivePromise =
object.Constructor('InteractivePromise', Promise, {
__message_handlers: null,
send: function(...args){
var that = this
;(this.__message_handlers || [])
.forEach(function(h){ h.call(that, ...args) })
return this },
then: IterablePromise.prototype.then,
//
// Promise.interactive(handler)
// -> interacive-promise
//
// handler(resolve, reject, onmessage)
//
// onmessage(func)
//
//
__new__: function(_, handler){
var handlers = []
var onmessage = function(func){
handlers.push(func) }
var obj = Reflect.construct(
InteractivePromise.__proto__,
!handler ?
[]
: [function(resolve, reject){
return handler(resolve, reject, onmessage) }],
InteractivePromise)
Object.defineProperty(obj, '__message_handlers', {
value: handlers,
enumerable: false,
})
return obj },
})
//---------------------------------------------------------------------
var CooperativePromise =
module.CooperativePromise =
object.Constructor('CooperativePromise', Promise, {
__handlers: null,
get isSet(){
return this.__handlers === false },
set: function(value, resolve=true){
// can't set twice...
if(this.isSet){
throw new Error('.set(..): can not set twice') }
// bind to promise...
if(value && value.then && value.catch){
value.then(handlers.resolve)
value.catch(handlers.reject)
// resolve with value...
} else {
resolve ?
this.__handlers.resolve(value)
: this.__handlers.reject(value) }
// cleanup and prevent setting twice...
this.__handlers = false
return this },
then: IterablePromise.prototype.then,
__new__: function(){
var handlers
var resolver = arguments[1]
var obj = Reflect.construct(
CooperativePromise.__proto__,
[function(resolve, reject){
handlers = {resolve, reject}
// NOTE: this is here to support builtin .then(..)
resolver
&& resolver(resolve, reject) }],
CooperativePromise)
Object.defineProperty(obj, '__handlers', {
value: handlers,
enumerable: false,
writable: true,
})
return obj },
})
//---------------------------------------------------------------------
var PromiseMixin =
module.PromiseMixin =
object.Mixin('PromiseMixin', 'soft', {
// XXX does this need to be a distinct object/constructor???
cooperative: function(){
var handlers
return object.mixinFlat(
new Promise(function(resolve, reject){
handlers = { resolve, reject, } }),
{
get isSet(){
return handlers === false },
//
// Resolve promise with value...
// .set(value)
// -> this
//
// Reject promise with value...
// .set(value, false)
// -> this
//
set: function(value, resolve=true){
// can't set twice...
if(this.isSet){
throw new Error('Promise.cooperative().set(..): can not set twice') }
// bind to promise...
if(value && value.then && value.catch){
value.then(handlers.resolve)
value.catch(handlers.reject)
// resolve with value...
} else {
resolve ?
handlers.resolve(value)
: handlers.reject(value) }
// cleanup and prevent setting twice...
handlers = false
return this },
}) },
iter: IterablePromise,
interactive: InteractivePromise,
cooperative: CooperativePromise,
})

View File

@ -46,14 +46,13 @@ var Eventfull =
module.Eventfull =
function(name, func, options={}){
var hidden
var method
options = func && typeof(func) != 'function' ?
func
: options
return object.mixinFlat(
method = function(...args){
var method = object.mixin(
function(...args){
var handlers =
// hidden...
options.handlerLocation == 'hidden' ?
@ -107,8 +106,14 @@ function(name, func, options={}){
: options.handlerLocation == 'method' ?
(method.__event_handlers__ = method.__event_handlers__ || [])
// context (default)...
: ((context.__event_handlers__ = context.__event_handlers__ || {})[name] =
context.__event_handlers__[name] || [])
: (context.__event_handlers__ == null ?
Object.defineProperty(context, '__event_handlers__', {
value: {[name]: (handlers = [])},
enumerable: false,
})
&& handlers
: (context.__event_handlers__[name] =
context.__event_handlers__[name] || []))
// add handler...
handlers.push(func)
return this },
@ -128,7 +133,13 @@ function(name, func, options={}){
toString: function(){
return func.toString()
.replace(/^(function[^(]*\()[^,)]*, ?/, '$1') },
}) }
})
Object.defineProperty(method, 'name', {
value: name,
})
return method }
module.TRIGGER = {doc: 'force event method to trigger'}
@ -169,6 +180,8 @@ module.TRIGGER = {doc: 'force event method to trigger'}
// arg is a function or not...
//
//
// XXX might be a good idea to adde an event that can't be triggered by
// calling...
var Event =
module.Event =
function(name, func, options={}){
@ -177,7 +190,8 @@ function(name, func, options={}){
func
: options
return Object.assign(
//return Object.assign(
return object.mixin(
method = Eventfull(name,
function(handle, ...args){
// add handler...
@ -199,8 +213,10 @@ function(name, func, options={}){
// NOTE: this is a copy of Eventfull's .toString() as we
// still need to base the doc on the user's func...
toString: function(){
return func.toString()
.replace(/^(function[^(]*\()[^,)]*, ?/, '$1') },
return func ?
func.toString()
.replace(/^(function[^(]*\()[^,)]*, ?/, '$1')
: `function ${name}(){}`},
}) }
@ -213,7 +229,7 @@ function(name, func, options={}){
// XXX do we need to be able to force global handler???
var EventHandlerMixin =
module.EventHandlerMixin = object.Mixin('EventHandlerMixin', {
__event_handlers__: null,
//__event_handlers__: null,
on: function(evt, func){
// event...
@ -222,7 +238,12 @@ module.EventHandlerMixin = object.Mixin('EventHandlerMixin', {
this[evt].__event_handler_add__(this, func)
// non-event...
} else {
;((this.__event_handlers__ = this.__event_handlers__ || {})[evt] =
this.__event_handlers__ == null
&& Object.defineProperty(this, '__event_handlers__', {
value: {},
enumerable: false,
})
;(this.__event_handlers__[evt] =
this.__event_handlers__[evt] || [])
.push(func) }
return this },

8
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "ig-types",
"version": "3.7.14",
"version": "5.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -265,9 +265,9 @@
}
},
"ig-object": {
"version": "5.4.11",
"resolved": "https://registry.npmjs.org/ig-object/-/ig-object-5.4.11.tgz",
"integrity": "sha512-WPPQ5C41c6q3tPfa2fBbWE2xcLF7LoGRu2E6Wr/aoA5oxAyl8lAuE7Kqt4TyPwfW9jVI0+ifBztg9e1tR5mG1Q=="
"version": "5.4.12",
"resolved": "https://registry.npmjs.org/ig-object/-/ig-object-5.4.12.tgz",
"integrity": "sha512-9kZM80Js9/eTwXN9VXwLDC1wDJ7gIAdYU9GIzb5KJmNcLAMaW+zhgFrwFFMrcSfggUuadgnqSrS41E4XLe8JZw=="
},
"ig-test": {
"version": "1.4.8",

View File

@ -1,6 +1,6 @@
{
"name": "ig-types",
"version": "5.0.0",
"version": "5.0.2",
"description": "Generic JavaScript types and type extensions...",
"main": "main.js",
"scripts": {
@ -23,7 +23,7 @@
},
"homepage": "https://github.com/flynx/types.js#readme",
"dependencies": {
"ig-object": "^5.4.11",
"ig-object": "^5.4.12",
"object-run": "^1.0.1"
},
"devDependencies": {

151
runner.js
View File

@ -21,6 +21,7 @@
var object = require('ig-object')
require('./Array')
require('./Promise')
var events = require('./event')
@ -38,7 +39,8 @@ module.STOP = object.STOP
// XXX do we need an async mode -- exec .__run_tasks__(..) in a
// setTimeout(.., 0)???
var Queue =
module.Queue = object.Constructor('Queue', Array, {
module.Queue =
object.Constructor('Queue', Array, {
// create a running queue...
runTasks: function(...tasks){
return this({ state: 'running' }, ...tasks) },
@ -106,6 +108,16 @@ module.Queue = object.Constructor('Queue', Array, {
queueEmpty: events.Event('queueEmpty'),
// NOTE: each handler will get called once when the next time the
// queue is emptied...
// XXX revise...
then: function(func){
var that = this
return new Promise(function(resolve, reject){
that.one('queueEmpty', function(){
resolve(func()) }) }) },
// helpers...
//
// move tasks to head/tail of queue resp.
@ -142,7 +154,7 @@ module.Queue = object.Constructor('Queue', Array, {
task.start()
: task },
//
// Hanlde 'running' state...
// Hanlde 'running' state (async)...
// .__run_tasks__()
// -> this
//
@ -152,36 +164,33 @@ module.Queue = object.Constructor('Queue', Array, {
__running: null,
__run_tasks__: function(){
var that = this
this.state == 'running'
&& setTimeout(function(){
// handle queue...
while(this.length > 0
&& this.state == 'running'
&& (this.__running || []).length < (this.pool_size || Infinity) ){
this.runTask(this.__run_tasks__.bind(this)) }
// if we are not running stop immidiately...
if(this.state != 'running'){
return this }
// handle queue...
while(this.length > 0
&& this.state == 'running'
&& (this.__running || []).length < (this.pool_size || Infinity) ){
this.runTask(this.__run_tasks__.bind(this)) }
// empty queue -> pole or stop...
//
// NOTE: we endup here in two cases:
// - the pool is full
// - the queue is empty
// NOTE: we do not care about stopping the timer when changing
// state as .__run_tasks__() will stop itself...
//
// XXX will this be collected by the GC if it is polling???
if(this.length == 0
&& this.state == 'running'){
this.auto_stop ?
// auto-stop...
this.stop()
// pole...
: (this.poling_delay
&& setTimeout(
this.__run_tasks__.bind(this),
this.poling_delay || 200)) }
// empty queue -> pole or stop...
//
// NOTE: we endup here in two cases:
// - the pool is full
// - the queue is empty
// NOTE: we do not care about stopping the timer when changing
// state as .__run_tasks__() will stop itself...
//
// XXX will this be collected by the GC if it is polling???
if(this.length == 0
&& this.state == 'running'){
this.auto_stop ?
// auto-stop...
this.stop()
// pole...
: (this.poling_delay
&& setTimeout(
this.__run_tasks__.bind(this),
this.poling_delay || 200)) } }.bind(this), 0)
return this },
// run one task from queue...
@ -291,6 +300,86 @@ module.Queue = object.Constructor('Queue', Array, {
}))
//---------------------------------------------------------------------
// Task manager...
//
// goal:
// externally manage long running functions/promises/etc
//
// enteties:
// task
// - wrap a function/promise
// - pass the function/promise a reciver
// - return a controller (store in manager)
// manager
// - container for tasks
// - multiplex actions to tasks
//
var TaskMixin =
object.Mixin('TaskMixin', 'soft', {
stop: function(){
this.send('stop', ...arguments) },
})
// XXX should this be a Queue???
var TaskManager =
module.TaskManager =
object.Constructor('TaskManager', Array, events.EventMixin('flat', {
// XXX each task should also trigger this when stopping and this
// should not result in this and tasks infinitely playing
// ping-pong...
stop: events.Event('stop',
function(task='all'){
this.forEach(function(task){
;(task == 'all'
|| task == '*'
|| task === task)
&& task.stop() }) }),
done: events.Event('done'),
Task: function(task, ...args){
var that = this
// normalize handler...
var handler =
// queue...
// NOTE: queue is task-compatible...
task instanceof Queue ?
task.start()
// interactive...
: task && task.then && task.stop ?
task
: TaskMixin(
// dumb promise -- will ignore all the messages...
// XXX should we complain about this???
task instanceof Promise ?
Promise.interactive(
function(resolve, reject, onmsg){
task.then(resolve, reject) })
// function...
: Promise.interactive(
function(resolve, reject, onmsg){
resolve(task(onmsg, ...args)) }))
this.push(handler)
// handle task done...
handler
.then(function(res){
that.splice(that.indexOf(handler), 1)
that.trigger('done', task, res)
that.length == 0
&& that.done('all') })
// XXX or should we return this???
return handler },
}))