Сохранение ответа REST для indexedDB с помощью Cycle.js

Я в процессе изучения Cycle.JS и столкнулся с проблемой. У меня есть компонент, который получит результат от HTTP-вызова, и я хотел бы сохранить этот ответ в indexDB. Однако я чувствую, что запрос на постоянство является обязанностью другого компонента.

У меня есть следующие вопросы:

  1. Это вариант использования для пользовательского драйвера, который сохраняет HTTP-ответы на indexDB?
  2. Как другой компонент получает доступ к потоку ответов на запрос, который он не сделал?
  3. Когда я пытаюсь выбрать категорию из источника HTTP, на консоли ничего не регистрируется. Я использую xstream, поэтому потоки должны быть горячими, и я ожидаю отладки для вывода. Что тут происходит?

Ниже мой компонент, который делает HTTP-вызов:

import { Feed } from './feed'

export function RssList ({HTTP, props}, feedAdapter = x => x) {
  const request$ = props.url$
    .map(url => ({
      url: url,
      method: 'GET',
      category: 'rss'
    }))

  const response$ = HTTP
    .select('rss')
    .flatten()
    .map(feedAdapter)

  const vDom$ = response$
    .map(Feed)
    .startWith('')

  return {
    DOM: vDom$,
    HTTP: request$
  }
}

Вот моя попытка получить доступ к ответу на уровне приложения:

export function main (sources) {
  const urlSource = url$(sources)
  const rssSink = rss$(sources, urlSource.value)

  const vDom$ = xs.combine(urlSource.DOM, rssSink.DOM)
    .map(([urlInput, rssList]) =>
      <div>
        {urlInput}
        {rssList}
      </div>
    )

  sources.HTTP.select('rss').flatten().debug() // nothing happens here

  return {
    DOM: vDom$,
    HTTP: rssSink.HTTP
  }
}

2 ответа

Решение

Выбор категории в основном (родительском) компоненте является правильным подходом и поддерживается.

Единственная причина, почему sources.HTTP.select('rss').flatten().debug() ничего не регистрируется, потому что это не так debug работает. Он не "подписывается" на поток и создает побочные эффекты. debug по сути, как map оператор, который использует функцию тождества (всегда принимает x в качестве входа и выхода x), но с регистрацией в качестве побочного эффекта. Так что вам либо нужно заменить .debug() с .addListener({next: x => console.log(x)}) или использовать поток, который .debug() выводит и подключает его с конвейером оператора, который идет в приемники. Другими словами, debug является промежуточным побочным эффектом, а не целевым побочным эффектом.

Вопрос № 1: Пользовательский драйвер HTTP->IDB: Это зависит от характера проекта, для простого примера я использовал общий драйвер CycleJS IDB. См. Пример ниже или код codeandbox.io.

Вопрос № 2: потоки совместного использования компонентов: поскольку компоненты и main использовать тот же API источника / приемника, с которым вы можете связать вывод (sink) одного компонента на вход (source) другого. См. Пример ниже или код codeandbox.io.

Вопрос № 3: debug и ведение журнала: как отметил авторитетный ( буквально) Андре Штальц debug должен быть вставлен в завершенный цикл потока, т.е. IE, уже подписанный / прослушиваемый поток.

В вашем примере вы можете положить debug в вашем RssList составная часть:

const response$ = HTTP
  .select('rss')
  .flatten()
  .map(feedAdapter)
  .debug()

ИЛИ добавить слушателя к вашему main пример:

sources.HTTP.select('rss').flatten().debug()
  .addListener({next: x => console.log(x)})

ИЛИ, что мне нравится делать, это включить драйвер журнала:

run(main, {
    DOM: makeDOMDriver('#app'),
    HTTP: makeHTTPDriver(),
    log: log$ => log$.addListener({next: log => console.log(log)}),
})

Затем я просто продублирую поток и отправлю его log раковина:

const url$ = props.url
const http$ = url$.map(url => ({url: url, method: 'GET', category: 'rss'}))
const log$ = url$

return {
  DOM: vdom$,
  HTTP: http$,
  log: log$,
}

Вот пример кода для отправки ответа HTTP в хранилище IndexedDB с использованием двух компонентов, которые совместно используют данные, и общего драйвера IndexedDB:

function main(sources) {
  const header$ = xs.of(div('RSS Feed:'))

  const rssSink = RssList(sources) // input HTTP select and props
                                   // output VDOM and data for IDB storage
  const vDom$ = xs.combine(header$, rssSink.DOM) // build VDOM
    .map(([header, rssList]) => div([header, rssList])
  )
  const idbSink = IdbSink(sources, rssSink.IDB) // output store and put HTTP response

  return {
    DOM: vDom$,
    HTTP: rssSink.HTTP, // send HTTP request
    IDB: idbSink.put, // send response to IDB store
    log: idbSink.get, // get and log data stored in IDB
  }
}

function RssList({ HTTP, props }, feedAdapter = x => x) {
  const request$ = props.url$
    .map(url => ({url: url, method: 'GET', category: 'rss'}))

  const response$ = HTTP.select('rss').flatten().map(feedAdapter)
  const idb$ = response$
  const vDom$ = response$
    .map(Feed)
    .startWith(div('','...loading'))

  return {
    DOM: vDom$,
    HTTP: request$,
    IDB: { response: idb$ },
  }
}
function Feed (feed) {
  return div('> ' + feed)
}

function IdbSink(sources, idb) {
  return {
    get: sources.IDB.store('rss').getAll()
      .map(obj => (obj['0'] && obj['0'].feed) || 'unknown'),
    put: idb.response
      .map(feedinfo => $put('rss', { feed: feedinfo }))
  }
}

run(main, {
  props: () => ({ url$: xs.of('http://lorem-rss.herokuapp.com/feed') }),
  DOM: makeDOMDriver('#root'),
  HTTP: makeHTTPDriver(),
  IDB: makeIdbDriver('rss-db', 1, upgradeDb => {
    upgradeDb.createObjectStore('rss', { keyPath: 'feed' })
  }),
  log: log$ => log$.addListener({next: log => console.log(log)}),
})

Это надуманный пример, просто для изучения поднятых вопросов. Пример Codesandbox.io.

Другие вопросы по тегам