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.js
testing/
examples/

View File

@ -1,6 +1,6 @@
{
"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",
"main": "src/ready.js",
"scripts": {
@ -22,7 +22,7 @@
},
"homepage": "https://github.com/uCOMmandIt/uci-utils#readme",
"dependencies": {
"@uci-utils/to-boolean": "^0.1.1",
"@uci-utils/to-boolean": "^0.1.5",
"is-observable": "^2.0.0",
"is-plain-object": "^3.0.0",
"p-is-promise": "^3.0.0",
@ -31,7 +31,7 @@
"devDependencies": {
"chai": "^4.2.0",
"esm": "^3.2.25",
"mocha": "^6.2.2",
"nodemon": "^1.19.4"
"mocha": "^7.0.1",
"nodemon": "^2.0.2"
}
}

View File

@ -8,12 +8,14 @@ import isPlainObject from 'is-plain-object'
import { createBoolean } from '@uci-utils/to-boolean'
class Ready extends Map {
constructor(opts) {
constructor(opts={}) {
super(opts.observables)
this.emitter = isEmitter(opts.emitter) ? opts.emitter : null
const toBool = createBoolean(opts.boolean)
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 (Array.isArray(value)) value=value[0]
return toBool(value)
}
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)
this.handler = opts.handler || ((ready) => {console.log('default handler', ready)})
this._first = true // tracks first emission
console.log('@uci-utils/ready package tag 0.1.10')
}
get observers(){return Array.from(this.keys())}
get combinations(){return Array.from(this._combinations.keys())}
get all() { return this._all}
get failure () {
let ret = null
let failed = this.state.some(obs=> {
ret = obs[0]
return obs[1]===false
})
return !failed ? '__none__' : ret
get failed() {
// console.log('making failures from',this.state)
let failed = this.state
.filter(([,ready]) => {
// console.log('in filter', ready)
// return !await this.getValue(name)
// 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}
@ -67,9 +76,10 @@ class Ready extends Map {
getValue(name) { // NOT recommended. if there is any issue will return false
let obs = this.getObserver(name)
return new Promise(resolve => {
setTimeout(()=>resolve(false),50)
setTimeout(()=>resolve(false),100)
if (isObservable(obs)){
const sub = obs.subscribe(val => {
console.log('in value subscriber', name,val)
resolve(val)
})
sub.unsubscribe()
@ -85,13 +95,18 @@ class Ready extends Map {
condition = condition || this.condition
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 (!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
let xobs = obs.pipe(
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
map(condition),
// tap(val => console.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),
// shareReplay({refCount:true, bufferSize:1}),
shareReplay(1),
@ -131,6 +146,19 @@ class Ready extends Map {
// 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
combineObservers(name,list,opts={}) {
if (!name || typeof(name)!=='string') return false // name required
@ -141,14 +169,7 @@ class Ready extends Map {
else return false
}
let observers = list.map(name=>{return name._readyType ? name : this.getObserver(name)})
observers = observers.filter(obs=>obs._readyType)
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)
)
const combination = this.makeCombination(observers,list)
this._combinations.set(name, combination) // if name passed then save combo in Map
return combination
}