[Javascript] Broadcaster + Operator + Listener pattern -- 24. Design choice, ifElse or merge
In previous post, we check how to use ifElse to branch out the logic: https://www.cnblogs.com/Answer1215/p/14093562.html
let inputToBooks = pipe( waitFor(150), ifElse( // condition name => name.length > 3, // if pipe( map(name => `https://openlibrary.org/search.json?q=${name}`), mapBroadcaster(getUrl), map(json => json.docs) ), // else map(() => []) ))(inputValue)
instead of using 'ifElse', we can split the logic into two operators, in the end, merge those back together:
let inputToBooks = pipe( filter(name => name.length > 3), waitFor(150), pipe( map(name => `https://openlibrary.org/search.json?q=${name}`), mapBroadcaster(getUrl), map(json => json.docs) ))(inputValue) let inputToClearSearch = pipe( filter(name => name.length < 4), map(() => [{title: "hello"}]) )(inputValue) let books = useBroadcaster(merge( inputToBooks, inputToClearSearch ), [])
Personally I previous using 'merge' approach.
import React from "react"
import { render } from "react-dom"
import {
useBroadcaster,
useListener,
merge,
} from "./broadcasters"
import { targetValue, waitFor, mapBroadcaster, map, filter} from "./operators"
import {pipe} from "lodash/fp"
//https://openlibrary.org/search.json?q=${name}
export let mapError = transform => broadcaster => listener => {
return broadcaster((value) => {
if (value instanceof Error) {
listener(transform(value))
return
}
listener(value)
})
}
let getUrl = url => listener => {
let controller = new AbortController()
let signal = controller.signal
fetch(url, {signal})
.then((response) => {
return response.json()
})
.then(listener)
.catch(listener)
return () => {
controller.abort()
}
}
let App = () => {
let onInput = useListener()
let inputValue = targetValue(onInput)
let inputToBooks = pipe(
filter(name => name.length > 3),
waitFor(150),
pipe(
map(name => `https://openlibrary.org/search.json?q=${name}`),
mapBroadcaster(getUrl),
map(json => json.docs)
))(inputValue)
let inputToClearSearch = pipe(
filter(name => name.length < 4),
map(() => [{title: "hello"}])
)(inputValue)
let books = useBroadcaster(merge(
inputToBooks,
inputToClearSearch
), [])
return (
<div>
<input type="text" onInput={onInput} />
{books.map(book => {
return <div key={book.title}>
<a href={`https://openlibrary.org${book.key}`}>{book.title}</a>
</div>
})}
</div>
)
}
render(<App></App>, document.querySelector("#root"))
broadcasters.js;
import { curry } from "lodash"
import React, {useState, useEffect, useCallback} from "react"
export let done = Symbol("done")
export let createTimeout = curry((time, listener) => {
let id = setTimeout(() => {
listener(null)
listener(done)
}, time)
return () => {
clearTimeout(id)
}
})
export let addListener = curry(
(selector, eventType, listener) => {
let element = document.querySelector(selector)
element.addEventListener(eventType, listener)
return () => {
element.removeEventListener(eventType, listener)
}
}
)
export let createInterval = curry((time, listener) => {
let i = 0
let id = setInterval(() => {
listener(i++)
}, time)
return () => {
clearInterval(id)
}
})
//broadcaster = function that accepts a listener
export let merge = curry(
(broadcaster1, broadcaster2, listener) => {
let cancel1 = broadcaster1(listener)
let cancel2 = broadcaster2(listener)
return () => {
cancel1()
cancel2()
}
}
)
export let zip = curry(
(broadcaster1, broadcaster2, listener) => {
let cancelBoth
let buffer1 = []
let cancel1 = broadcaster1(value => {
buffer1.push(value)
// console.log(buffer1)
if (buffer2.length) {
listener([buffer1.shift(), buffer2.shift()])
if (buffer1[0] === done || buffer2[0] === done) {
listener(done)
cancelBoth()
}
}
})
let buffer2 = []
let cancel2 = broadcaster2(value => {
buffer2.push(value)
if (buffer1.length) {
listener([buffer1.shift(), buffer2.shift()])
if (buffer1[0] === done || buffer2[0] === done) {
listener(done)
cancelBoth()
}
}
})
cancelBoth = () => {
cancel1()
cancel2()
}
return cancelBoth
}
)
export let forOf = curry((iterable, listener) => {
let id = setTimeout(() => {
for (let i of iterable) {
listener(i)
}
listener(done)
}, 0)
return () => {
clearTimeout(id)
}
})
export let useBroadcaster = (broadcaster, initVal = null, deps = []) => {
let [state, setState] = useState(initVal)
useEffect(() => {
broadcaster((value) => {
if (value === done) {
return
}
setState(value)
})
}, deps)
return state
}
export let useListener = (deps = []) => {
let listeners = []
let callbackListener = value => {
if (typeof value === "function") {
listeners.push(value)
return
}
listeners.forEach(listener => listener(value))
}
return useCallback(callbackListener, deps)
}
operators.js:
import { curry } from "lodash"
import { done, createTimeout } from "./broadcasters"
let createOperator = curry(
(operator, broadcaster, listener) => {
return operator(behaviorListener => {
return broadcaster(value => {
if (value === done) {
listener(done)
return
}
behaviorListener(value)
})
}, listener)
}
)
export let map = transform =>
createOperator((broadcaster, listener) => {
return broadcaster(value => {
listener(transform(value))
})
})
export let filter = predicate =>
createOperator((broadcaster, listener) => {
return broadcaster(value => {
if (predicate(value)) {
listener(value)
}
})
})
export let split = splitter =>
curry((broadcaster, listener) => {
let buffer = []
return broadcaster(value => {
if (value === done) {
listener(buffer)
buffer = []
listener(done)
}
if (value == splitter) {
listener(buffer)
buffer = []
} else {
buffer.push(value)
}
})
})
export let hardCode = newValue =>
createOperator((broadcaster, listener) => {
return broadcaster(value => {
listener(newValue)
})
})
export let add = initial => broadcaster => listener => {
return broadcaster(value => {
listener((initial += value))
})
}
export let startWhen = whenBroadcaster => mainBroadcaster => listener => {
let cancelMain
let cancelWhen
cancelWhen = whenBroadcaster(whenValue => {
if (cancelMain) cancelMain()
cancelMain = mainBroadcaster(value => {
if (value === done) {
if (whenValue === done) {
listener(done)
}
return
}
listener(value)
})
})
return () => {
cancelMain()
cancelWhen()
}
}
export let stopWhen = whenBroadcaster => mainBroadcaster => listener => {
let cancelMain = mainBroadcaster(listener)
let cancelWhen = whenBroadcaster(value => {
cancelMain()
})
return () => {
cancelMain()
cancelWhen()
}
}
export let targetValue = map(event => event.target.value)
export let mapBroadcaster = createBroadcaster => broadcaster => listener => {
return broadcaster(value => {
let newBroadcaster = createBroadcaster(value)
newBroadcaster(listener)
})
}
export let applyOperator = broadcaster =>
mapBroadcaster(operator => operator(broadcaster))
export let stringConcat = broadcaster => listener => {
let result = ""
return broadcaster(value => {
if (value === done) {
listener(result)
result = ""
return
}
result += value
})
}
export let repeat = broadcaster => listener => {
let cancel
let repeatListener = value => {
if (value === done) {
cancel()
cancel = broadcaster(repeatListener)
return
}
listener(value)
}
cancel = broadcaster(repeatListener)
return cancel
}
export let repeatWhen = whenBroadcaster => broadcaster => listener => {
let cancel
let cancelWhen
let repeatListener = value => {
if (value === done) {
cancel()
cancelWhen = whenBroadcaster(() => {
cancelWhen()
cancel = broadcaster(repeatListener)
})
return
}
listener(value)
}
cancel = broadcaster(repeatListener)
return () => {
cancel()
if (cancelWhen) cancelWhen()
}
}
export let state = broadcaster => listener => {
let state = 3
return broadcaster(value => {
state--
listener(state)
})
}
export let doneIf = condition => broadcaster => listener => {
let cancel = broadcaster(value => {
listener(value)
if (condition(value)) {
listener(done)
cancel()
}
})
return cancel
}
export let sequence = (...broadcasters) => listener => {
let broadcaster = broadcasters.shift()
let cancel
let sequenceListener = value => {
if (value === done && broadcasters.length) {
let broadcaster = broadcasters.shift()
cancel = broadcaster(sequenceListener)
return
}
listener(value)
}
cancel = broadcaster(sequenceListener)
return () => {
cancel()
}
}
export let mapSequence = createBroadcaster => broadcaster => listener => {
let cancel
let buffer = []
let innerBroadcaster
let innerListener = innerValue => {
if (innerValue === done) {
innerBroadcaster = null
if (buffer.length) {
let value = buffer.shift()
if (value === done) {
listener(done)
return
}
innerBroadcaster = createBroadcaster(value)
cancel = innerBroadcaster(innerListener)
}
return
}
listener(innerValue)
}
broadcaster(value => {
if (innerBroadcaster) {
buffer.push(value)
} else {
innerBroadcaster = createBroadcaster(value)
cancel = innerBroadcaster(innerListener)
}
})
return () => {
cancel()
}
}
export const filterByKey = key => filter(event => event.key === key)
export const allowWhen = allowBroadcaster => broadcaster => listener => {
let current
let cancel = broadcaster((value) => {
current = value;
})
let cancelAllow = allowBroadcaster(() => {
listener(current)
})
return () => {
cancel()
cancelAllow()
}
}
export let waitFor = time => broadcaster => listener => {
let cancelTimeout;
let cancel
cancel = broadcaster(value => {
if (cancelTimeout) {
cancelTimeout()
}
cancelTimeout = createTimeout(time)((innerValue) => {
if (innerValue === done) {
return
}
listener(value)
})
})
return () => {
cancel()
cancelTimeout()
}
}
export let ifElse = (condition, ifOp, elOp) => broadcaster => listener => {
let cancel = broadcaster(value => {
if (value === done) {
return;
}
if (condition(value)) {
ifOp(innerValue => innerValue(value))(listener)
} else {
elOp(innerValue => innerValue(value))(listener)
}
})
return () => {
cancel()
}
}

浙公网安备 33010602011771号