使用切换 start/stop 按钮停止 API 调用 Rxjs
stop API calls using a toggle start/stop button Rxjs
我一直在学习 Rxjs,并创建了一个小幻灯片,在单击开始按钮后每 5 秒刷新一次图像。再次单击按钮后,我如何才能将 API 变为 stop/pause?
import './style.css';
import { tap, switchMap } from 'rxjs/operators';
import { fromEvent, Observable, timer } from 'rxjs';
function updateImages(
links: string[]
// link1: string,
// link2: string,
// link3: string
): void {
document.getElementById('slideshow').childNodes.forEach((node: ChildNode) => {
if (node.nodeType == Node.ELEMENT_NODE) {
if (links.length) {
let element: HTMLElement = node as HTMLElement;
element.classList.add('loading');
element.style.backgroundImage = "url('" + links.shift() + "')";
element.classList.remove('loading');
}
}
});
}
//api returns message w jpeg url & status
const apiUrl: string = 'https://dog.ceo/api/breeds/image/random';
const btn = document.getElementById('btn'); // get the button element
const btnEvents$ = fromEvent(btn, 'click');
const sub = btnEvents$.subscribe((result) => {
startPolling('dogs').subscribe((dogs) => {
updateImages(dogs.map(({ message }) => message));
});
btn.innerHTML = 'Stop';
console.log(btn.classList);
});
function requestData(url: string): Observable<Array<{ message: string }>> {
return Observable.create((observer) => {
Promise.all([
fetch(url).then((response) => response.json()),
fetch(url).then((response) => response.json()),
fetch(url).then((response) => response.json()),
fetch(url).then((response) => response.json()),
])
.then((dogs) => {
console.log(dogs);
observer.next(dogs);
observer.complete();
})
.catch((err) => observer.error(err));
}).pipe(tap((data) => console.log('dogrequest', data)));
}
function startPolling(
category: string,
interval: number = 5000
): Observable<Array<{ message: string }>> {
const url = category === 'dogs' ? apiUrl : null;
console.log(url);
return timer(0, interval).pipe(switchMap((_) => requestData(url)));
}
目前它一直在获取数据,我无法让它停止。
如果我理解你想要实现的目标,你有一个按钮,它的行为就像一个开关,在某种意义上,当它被点击时它开始幻灯片放映(打开)并且当它再次被点击时它停止幻灯片(关闭)。
如果我的理解是正确的,这是使用 switchMap
运算符的一个很好的例子。
让我们看看如何实现。注释解释代码
// fist of all we define a variable holding the state of the switch
// there are more 'rxJs idiomatic' ways to manage the state, but for the moment
// lets stay with a variable holding the state
let on = false;
// this is your code that defines the btnEvents$ Observable which is the
// starting point of our stream
const btn = document.getElementById('btn'); // get the button element
const btnEvents$ = fromEvent(btn, 'click');
// this is the core stream that implements our solution
// it starts with the stream of 'click' events
btnEvents$
.pipe(
// the tap operator allows us to implement side effects
// in this particular case every time a click is notified we need to
// change the state (switch on/off) and set the label of the button
tap(() => {
on = !on;
btn.innerHTML = on ? 'Stop' : 'Start';
}),
// here is where the switch happens
// every time a click is notified by upstream, the switchMap operator
// unsubscribes the previous Observable and subscribes to a new one
switchMap(() => {
// this means that when a new click event is notified by upstream
// we check the state
// if the state is "on" we return the stream that fetches the data
// otherwise we return an Observable that notifies just an empty array
return on ? requestData("dogsUrl") : of([]);
}),
// finally last side effect, i.e. we update the images based on the
// arrays of dogs returned
// consider that if the state is "off" than the "dogs" array is empty
// and therefore no update is performed
tap(dogs => updateImages(dogs.map(({ message }) => message));)
)
// now that we have built the Observable as per our requirements,
// we just have to subscribe to it to trigger the entire execution
.subscribe(console.log);
实现不完整,例如缺少错误处理逻辑,但我希望足以阐明思路
我一直在学习 Rxjs,并创建了一个小幻灯片,在单击开始按钮后每 5 秒刷新一次图像。再次单击按钮后,我如何才能将 API 变为 stop/pause?
import './style.css';
import { tap, switchMap } from 'rxjs/operators';
import { fromEvent, Observable, timer } from 'rxjs';
function updateImages(
links: string[]
// link1: string,
// link2: string,
// link3: string
): void {
document.getElementById('slideshow').childNodes.forEach((node: ChildNode) => {
if (node.nodeType == Node.ELEMENT_NODE) {
if (links.length) {
let element: HTMLElement = node as HTMLElement;
element.classList.add('loading');
element.style.backgroundImage = "url('" + links.shift() + "')";
element.classList.remove('loading');
}
}
});
}
//api returns message w jpeg url & status
const apiUrl: string = 'https://dog.ceo/api/breeds/image/random';
const btn = document.getElementById('btn'); // get the button element
const btnEvents$ = fromEvent(btn, 'click');
const sub = btnEvents$.subscribe((result) => {
startPolling('dogs').subscribe((dogs) => {
updateImages(dogs.map(({ message }) => message));
});
btn.innerHTML = 'Stop';
console.log(btn.classList);
});
function requestData(url: string): Observable<Array<{ message: string }>> {
return Observable.create((observer) => {
Promise.all([
fetch(url).then((response) => response.json()),
fetch(url).then((response) => response.json()),
fetch(url).then((response) => response.json()),
fetch(url).then((response) => response.json()),
])
.then((dogs) => {
console.log(dogs);
observer.next(dogs);
observer.complete();
})
.catch((err) => observer.error(err));
}).pipe(tap((data) => console.log('dogrequest', data)));
}
function startPolling(
category: string,
interval: number = 5000
): Observable<Array<{ message: string }>> {
const url = category === 'dogs' ? apiUrl : null;
console.log(url);
return timer(0, interval).pipe(switchMap((_) => requestData(url)));
}
目前它一直在获取数据,我无法让它停止。
如果我理解你想要实现的目标,你有一个按钮,它的行为就像一个开关,在某种意义上,当它被点击时它开始幻灯片放映(打开)并且当它再次被点击时它停止幻灯片(关闭)。
如果我的理解是正确的,这是使用 switchMap
运算符的一个很好的例子。
让我们看看如何实现。注释解释代码
// fist of all we define a variable holding the state of the switch
// there are more 'rxJs idiomatic' ways to manage the state, but for the moment
// lets stay with a variable holding the state
let on = false;
// this is your code that defines the btnEvents$ Observable which is the
// starting point of our stream
const btn = document.getElementById('btn'); // get the button element
const btnEvents$ = fromEvent(btn, 'click');
// this is the core stream that implements our solution
// it starts with the stream of 'click' events
btnEvents$
.pipe(
// the tap operator allows us to implement side effects
// in this particular case every time a click is notified we need to
// change the state (switch on/off) and set the label of the button
tap(() => {
on = !on;
btn.innerHTML = on ? 'Stop' : 'Start';
}),
// here is where the switch happens
// every time a click is notified by upstream, the switchMap operator
// unsubscribes the previous Observable and subscribes to a new one
switchMap(() => {
// this means that when a new click event is notified by upstream
// we check the state
// if the state is "on" we return the stream that fetches the data
// otherwise we return an Observable that notifies just an empty array
return on ? requestData("dogsUrl") : of([]);
}),
// finally last side effect, i.e. we update the images based on the
// arrays of dogs returned
// consider that if the state is "off" than the "dogs" array is empty
// and therefore no update is performed
tap(dogs => updateImages(dogs.map(({ message }) => message));)
)
// now that we have built the Observable as per our requirements,
// we just have to subscribe to it to trigger the entire execution
.subscribe(console.log);
实现不完整,例如缺少错误处理逻辑,但我希望足以阐明思路