我需要创建一个可观察的,我可以"拉"数据,来自可分页的api。我只能为每个请求获取100个项目,我希望能够使用observable作为生成器函数(我可以在其上调用.next()
发出请求以获取下一个100项。
不幸的是,我无法找到一种方法来使用Rx。我认为可以使用受控制的观察者或主体。你们能告诉我一个例子。
这是我到目前为止所得到的:
function list(entityType, viewName, fetchAll = false) {
var skip = 0,
total = 0;
const subject = new Rx.Subject(),
response$ = subject
.takeWhile(() => skip <= total)
.startWith(skip)
.flatMap((skip) => fetchPagePromise(skip)),
next = () => subject.onNext(skip);
if (fetchAll) {
Rx.Observable.timer(100, 100).subscribe(() => next());
}
return {
data$: response$.map(response => response),
next: fetchAll === true ? undefined : next
};
function fetchPagePromise() {
let limit = 100,
obj = {
viewName, limit, skip
},
qs = objectToQueryString(obj);
return $http.get(`${apiBase}/api/data/${entityType}${qs}`).then((res) => {
total = res.data.Total;
skip += limit;
return res.data.Rows;
});
}
}
这种工作就像一台发电机。它返回一个Observable和next
处理程序。每当调用next
时,它会从api中提取下100个项目并推入Observable。此外,如果传递了第三个参数fetchAll
,那么它将继续获取数据,直到不再存在。令我感到害怕的是,在函数关闭中有两个变异变量 - skip
和total
,我不知道在异步/不可预测的环境中管理它们是否正常
答案 0 :(得分:2)
您通常希望避免的一件事是尝试将Rx变成普通的旧事件发射器。通常,当您尝试通过传递Observables
观察者界面手动触发Subjects
时,它是一个指示器。
您应该问问自己,我的数据来自哪里?什么叫next()
,什么称之为等等。经过足够多的调整后,你会发现这会引导你直接用Observable
包裹而不是明确地调用next()
。 。另外,我认为fetchAll
标志应该保留在外部。你只是通过传入一个标志将它变成一个void方法,使界面混乱。
所以我建议像这样重构:
Rx.Observable.prototype.lazyRequest = function(entityType, viewName, limit = 100) {
var source = this;
return Rx.Observable.create(obs => {
var response = source
//Skip is really just the (limit * index)
.map((x, i) => i * limit)
.flatMap((skip) => {
let obj = {viewName, skip, limit},
qs = objectToQueryString(obj);
//Handle promises implicitly
return $http.get(`${apiBase}/api/data/${entityType}${qs}`);
},
//Return this with our skip information
(skip, res) => {skip, res})
//Publish it so the stream get shared.
.publish();
//This will emit once once you are out of data
var stop = response.first(x => x.skip >= x.res.data.Total);
return new CompositeDisposable(
//Complete this stream when stop emits
response.takeUntil(stop)
//Downstream only cares about the data rows
.map(x => x.res.data.Rows)
.subscribe(obs),
//Hook everything up
response.connect());
});
}
然后你可以像这样使用它:
//An example of a "starting point", a button click
//Update the rows every time a new event comes through
Rx.Observable.fromEvent($button, 'click')
.startWith(0) //Inject some data into the pipeline
.lazyRequest(entityType, viewName)
.subscribe(/*Do something with the returned rows*/);
//Get all of the rows, will keep hitting the endpoint until it completes
Rx.Observable.interval(100)
.lazyRequest(entityType, viewName)
//Gather all the values into an array and emit that.
.toArray()
.subscribe();