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

 

posted @ 2020-12-08 22:28  Zhentiw  阅读(108)  评论(0编辑  收藏  举报