使用 Cycle.js 存储对 indexedDB 的 REST 响应
Storing REST response to indexedDB with Cycle.js
我正在学习 Cycle.JS 和 运行 挑战。我有一个将从 HTTP 调用获得结果的组件,我想将此响应保存在 indexDB 中。不过我觉得请求持久化是另外一个组件的职责。
我的问题是:
- 这是将 HTTP 响应保存到 indexDB 的自定义驱动程序的用例吗?
- 另一个组件如何访问它没有发出的请求的响应流?
- 当我尝试 select 来自 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
}
}
在主(父)组件中选择类别是正确的做法,并且受到支持。
sources.HTTP.select('rss').flatten().debug()
不记录任何内容的唯一原因是 debug
不是这样工作的。它不会 "subscribe" 到流中并产生副作用。 debug
本质上类似于使用恒等函数的 map
运算符(始终将 x
作为输入并输出 x
),但具有日志记录操作作为副作用。因此,您要么需要将 .debug()
替换为 .addListener({next: x => console.log(x)})
,要么使用 .debug()
输出的流并将其与进入接收器的运算符管道挂钩。换句话说,debug
是 in-between 日志记录副作用,而不是 destination 日志记录副作用。
问题 #1:自定义 HTTP->IDB 驱动程序:这取决于项目的性质,举个简单的例子我使用了一个通用的 CycleJS IDB Driver. See example below or codesandbox.io example.
问题 #2:组件共享流:由于组件和 main
共享相同的 source/sink API 您可以 link 输出 (sink
)一个组件到另一个组件的输入 (source
)。请参阅下面的示例或 codesandbox.io example.
问题 #3:debug
和日志记录:正如权威人士(literally) André Staltz 指出 debug
需要插入到一个完整的流循环中,即,已经 subscribed/listened流。
在您的示例中,您可以将 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 example.
我正在学习 Cycle.JS 和 运行 挑战。我有一个将从 HTTP 调用获得结果的组件,我想将此响应保存在 indexDB 中。不过我觉得请求持久化是另外一个组件的职责。
我的问题是:
- 这是将 HTTP 响应保存到 indexDB 的自定义驱动程序的用例吗?
- 另一个组件如何访问它没有发出的请求的响应流?
- 当我尝试 select 来自 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
}
}
在主(父)组件中选择类别是正确的做法,并且受到支持。
sources.HTTP.select('rss').flatten().debug()
不记录任何内容的唯一原因是 debug
不是这样工作的。它不会 "subscribe" 到流中并产生副作用。 debug
本质上类似于使用恒等函数的 map
运算符(始终将 x
作为输入并输出 x
),但具有日志记录操作作为副作用。因此,您要么需要将 .debug()
替换为 .addListener({next: x => console.log(x)})
,要么使用 .debug()
输出的流并将其与进入接收器的运算符管道挂钩。换句话说,debug
是 in-between 日志记录副作用,而不是 destination 日志记录副作用。
问题 #1:自定义 HTTP->IDB 驱动程序:这取决于项目的性质,举个简单的例子我使用了一个通用的 CycleJS IDB Driver. See example below or codesandbox.io example.
问题 #2:组件共享流:由于组件和 main
共享相同的 source/sink API 您可以 link 输出 (sink
)一个组件到另一个组件的输入 (source
)。请参阅下面的示例或 codesandbox.io example.
问题 #3:debug
和日志记录:正如权威人士(literally) André Staltz 指出 debug
需要插入到一个完整的流循环中,即,已经 subscribed/listened流。
在您的示例中,您可以将 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 example.