0.1.10 add makeCombination, fix bug in makeObserver, refactor failed, add sending boolean event as array where first element is ready value to check

master
David Kebler 2020-02-10 21:42:45 -08:00
parent 333d565894
commit 205f9a2ccd
3 changed files with 44 additions and 22 deletions

View File

@ -2,3 +2,4 @@ tests/
test/ test/
*.test.js *.test.js
testing/ testing/
examples/

View File

@ -1,6 +1,6 @@
{ {
"name": "@uci-utils/ready", "name": "@uci-utils/ready",
"version": "0.1.7", "version": "0.1.10",
"description": "A Class to Observe the reduced to boolean combined state of a map of observables", "description": "A Class to Observe the reduced to boolean combined state of a map of observables",
"main": "src/ready.js", "main": "src/ready.js",
"scripts": { "scripts": {
@ -22,7 +22,7 @@
}, },
"homepage": "https://github.com/uCOMmandIt/uci-utils#readme", "homepage": "https://github.com/uCOMmandIt/uci-utils#readme",
"dependencies": { "dependencies": {
"@uci-utils/to-boolean": "^0.1.1", "@uci-utils/to-boolean": "^0.1.5",
"is-observable": "^2.0.0", "is-observable": "^2.0.0",
"is-plain-object": "^3.0.0", "is-plain-object": "^3.0.0",
"p-is-promise": "^3.0.0", "p-is-promise": "^3.0.0",
@ -31,7 +31,7 @@
"devDependencies": { "devDependencies": {
"chai": "^4.2.0", "chai": "^4.2.0",
"esm": "^3.2.25", "esm": "^3.2.25",
"mocha": "^6.2.2", "mocha": "^7.0.1",
"nodemon": "^1.19.4" "nodemon": "^2.0.2"
} }
} }

View File

@ -8,12 +8,14 @@ import isPlainObject from 'is-plain-object'
import { createBoolean } from '@uci-utils/to-boolean' import { createBoolean } from '@uci-utils/to-boolean'
class Ready extends Map { class Ready extends Map {
constructor(opts) { constructor(opts={}) {
super(opts.observables) super(opts.observables)
this.emitter = isEmitter(opts.emitter) ? opts.emitter : null this.emitter = isEmitter(opts.emitter) ? opts.emitter : null
const toBool = createBoolean(opts.boolean) const toBool = createBoolean(opts.boolean)
this.toBoolean = (value) => { this.toBoolean = (value) => {
// can emit plain object with ready prop, or array with first value as ready value or just ready value
if (isPlainObject(value)) value = (value.ready || value.online || value.active || false) if (isPlainObject(value)) value = (value.ready || value.online || value.active || false)
if (Array.isArray(value)) value=value[0]
return toBool(value) return toBool(value)
} }
this.condition = opts.condition || ( (ev) => this.toBoolean(ev) ) this.condition = opts.condition || ( (ev) => this.toBoolean(ev) )
@ -27,19 +29,26 @@ class Ready extends Map {
if (opts.verbose||process.env.UCI_READY_VERBOSE==='true') this.logger.subscribe(console.log) if (opts.verbose||process.env.UCI_READY_VERBOSE==='true') this.logger.subscribe(console.log)
this.handler = opts.handler || ((ready) => {console.log('default handler', ready)}) this.handler = opts.handler || ((ready) => {console.log('default handler', ready)})
this._first = true // tracks first emission this._first = true // tracks first emission
console.log('@uci-utils/ready package tag 0.1.10')
} }
get observers(){return Array.from(this.keys())} get observers(){return Array.from(this.keys())}
get combinations(){return Array.from(this._combinations.keys())} get combinations(){return Array.from(this._combinations.keys())}
get all() { return this._all} get all() { return this._all}
get failure () { get failed() {
let ret = null // console.log('making failures from',this.state)
let failed = this.state.some(obs=> { let failed = this.state
ret = obs[0] .filter(([,ready]) => {
return obs[1]===false // console.log('in filter', ready)
}) // return !await this.getValue(name)
return !failed ? '__none__' : ret // ret = obs[0]
return !ready
})
.map(obs=> {return {name:obs[0], details: this.getObserverDetails(obs[0])} })
// console.log('failed as filtered', failed)
return failed.length ? failed : '__none__'
} }
getObserverDetails(name) { return (this.get(name)||{}).details} getObserverDetails(name) { return (this.get(name)||{}).details}
@ -67,9 +76,10 @@ class Ready extends Map {
getValue(name) { // NOT recommended. if there is any issue will return false getValue(name) { // NOT recommended. if there is any issue will return false
let obs = this.getObserver(name) let obs = this.getObserver(name)
return new Promise(resolve => { return new Promise(resolve => {
setTimeout(()=>resolve(false),50) setTimeout(()=>resolve(false),100)
if (isObservable(obs)){ if (isObservable(obs)){
const sub = obs.subscribe(val => { const sub = obs.subscribe(val => {
console.log('in value subscriber', name,val)
resolve(val) resolve(val)
}) })
sub.unsubscribe() sub.unsubscribe()
@ -85,13 +95,18 @@ class Ready extends Map {
condition = condition || this.condition condition = condition || this.condition
if (isEmitter(obs) && (event || name)) obs = fromEvent(obs, event || name ) // it's an emitter if (isEmitter(obs) && (event || name)) obs = fromEvent(obs, event || name ) // it's an emitter
if (isPromise(obs)) obs = from(obs) // it's a promise if (isPromise(obs)) obs = from(obs) // it's a promise
if (!obs && this.emitter && (event || name)) obs = fromEvent(this.emitter,(event || name)) if ((!obs || typeof obs==='string') && this.emitter && (event || name || obs)) obs = fromEvent(this.emitter,(event || name || obs))
if (!obs || !isObservable(obs)) return false if (!obs || !isObservable(obs)) return false
let xobs = obs.pipe( let xobs = obs.pipe(
tap(val => this.log(`${name} emitted/resolved the value =>${JSON.stringify(val)}`)), tap(val => this.log(`${name} emitted/resolved the value =>${JSON.stringify(val)}`)),
// tap(val => console.log(`${name} emitted/resolved the value =>${JSON.stringify(val)}`)),
// TODO retain plain object if it has a ready property // TODO retain plain object if it has a ready property
map(condition), map(condition),
// tap(val => console.log(`boolean: ${val}`)),
tap(val => this.log(`boolean: ${val}`)), tap(val => this.log(`boolean: ${val}`)),
map(val => {
// if (opts.reverse) console.log('reversing', val, opts.reverse ? !val : val)
return opts.reverse ? !val : val}),
startWith(false), startWith(false),
// shareReplay({refCount:true, bufferSize:1}), // shareReplay({refCount:true, bufferSize:1}),
shareReplay(1), shareReplay(1),
@ -131,6 +146,19 @@ class Ready extends Map {
// TODO update affected combinations // TODO update affected combinations
} }
// TODO have combineObservers call this
makeCombination(observers,list){
observers = observers.filter(obs=>obs._readyType)
if (!observers.length) return false
let combination = combineLatest(observers).pipe(
tap(states => { if (list) this.log(list.map((name,index) => [name,states[index]]))}),
map(states => states.reduce((res,state) => {return res && state},true)),
changed(), //filters out emission if it is unchanged from last TODO replace with distinctUntilChanged
shareReplay(1)
)
return combination
}
// TODO add option for whether changed is enabled (default) or not and store with combo for remake // TODO add option for whether changed is enabled (default) or not and store with combo for remake
combineObservers(name,list,opts={}) { combineObservers(name,list,opts={}) {
if (!name || typeof(name)!=='string') return false // name required if (!name || typeof(name)!=='string') return false // name required
@ -141,14 +169,7 @@ class Ready extends Map {
else return false else return false
} }
let observers = list.map(name=>{return name._readyType ? name : this.getObserver(name)}) let observers = list.map(name=>{return name._readyType ? name : this.getObserver(name)})
observers = observers.filter(obs=>obs._readyType) const combination = this.makeCombination(observers,list)
if (!observers.length) return false
let combination = combineLatest(observers).pipe(
tap(states => { this.log(list.map((name,index) => [name,states[index]]))}),
map(states => states.reduce((res,state) => {return res && state},true)),
changed(), //filters out emission if it is unchanged from last
shareReplay(1)
)
this._combinations.set(name, combination) // if name passed then save combo in Map this._combinations.set(name, combination) // if name passed then save combo in Map
return combination return combination
} }