[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() } }