zoukankan      html  css  js  c++  java
  • [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()
      }
    }
  • 相关阅读:
    [转]AsyncTask的用法
    [转]Android Service学习之本地服务
    强制页面运行于IE8模式下
    标签分类
    获取元素的文本
    遍历节点的API
    为IE的javascript提速
    我的选择器 获得经过标记的没有重复的tagName等于tag的元素集
    kangax 的javascript谜题
    自动执行函数
  • 原文地址:https://www.cnblogs.com/Answer1215/p/14106062.html
Copyright © 2011-2022 走看看