@pastelInc
function search(event) {
const xhr = new XMLHttpRequest();
const query = event.target.value;
const url = location.href;
xhr.onreadystatechange = function(){
if (this.readyState === 4 && this.status === 200) {
render(this.responseText);
}
};
xhr.open('GET', url, true);
xhr.send();
};
function listen(element, eventName) {
element.addEventListener(eventName, search, true);
}
const inputElement = document.querySelector('input');
listen(inputElement, 'keyup');
ユーザーのキータイピング
キータイピング停止
タイマーが止まっていたらタイマースタート
タイマーが0になったらリクエスト
タイマーが動いていたらタイマーストップ/リセット
class Timer {
private id;
constructor() {
this.id = null;
}
get isStarted() {
return this.id !== null;
}
start(cb, msec) {
this.id = setTimeout(cb, msec);
}
stop() {
clearInterval(this.id);
}
reset() {
this.id = null;
}
}
function search(event) {
const xhr = new XMLHttpRequest();
const query = event.target.value;
const url = location.href;
xhr.onreadystatechange = function(){
if (this.readyState === 4 && this.status === 200) {
render(this.responseText);
}
};
xhr.open('GET', url, true);
if (!timer.isStarted) {
timer.start(xhr.send, 500);
} else {
timer.stop();
timer.reset();
}
}
ユーザーのキータイピング
キータイピング停止
タイマーが止まっていたらタイマースタート
タイマーが0になったらCache評価開始
Cacheと同じクエリパラメータならリクエストしない
Cacheと異なるクエリパラメータならリクエスト
タイマーが動いていたらタイマーストップ/リセット
class CacheQuery {
private query;
constructor() {
this.query = '';
}
get myQuery() {
return this.query;
}
save(query) {
this.query = query;
}
}
function search(event) {
const xhr = new XMLHttpRequest();
const query = event.target.value;
const url = location.href;
xhr.onreadystatechange = function(){
if (xhr.readyState === 4 && xhr.status === 200) {
render(xhr.responseText);
}
};
xhr.open('GET', url, true);
if (!timer.isStarted) {
timer.start(() => {
if (cache.myQuery !== query) {
xhr.send();
cache.save(query);
}
});
} else {
timer.stop();
timer.reset();
}
}
リクエストとレスポンスをペアとするような状態を何処かで実現しなければいけない。
{
"id": 1 // リクエストのBodyにリクエストIDを設定する。
}
現実世界の時間変化 = 計算機内での時間変化として考えていました。
先程の例ではサーバと状態の同期(共有)が必要になりました。
他にもオブジェクトが互いに状態のやり取りを行う必要がありました。
私達が直感的に感じている順番で事象が起こるとは限らない。
Observableを実装すると先程の例のクラスが必要なくなります。
function listen(element, eventName) {
return Rx.Observable.create(observer => {
let handler = event => observer.next(event);
element.addEventListener(eventName, handler, true);
return _ => {
element.removeEventListener(eventName, handler, true);
};
});
}
function search(event) {
const xhr = new XMLHttpRequest();
const query = event.target.value;
const url = location.href;
const observable = Rx.Observable.create(observer => {
xhr.onreadystatechange = function(){
if (xhr.readyState === 4 && xhr.status === 200) {
observer.next(xhr);
}
};
});
xhr.open('GET', url, true);
xhr.send();
return observable;
}
const inputElement = document.querySelector('input');
const observable = listen(inputElement, 'keyup')
.debounceTime(500) // 500msの間、keyupがなければデータソースを発信
.distinctUntilChanged() // 重複したデータソースをフィルタ
.switchMap((event) => search(event)) // レスポンスの前後が逆になった場合、後だけを発信するようにフィルタ
;
observable.subscribe({
next: xhr => {
render(xhr.responseText);
}
});
DOMイベント、timer intervals、socketのようなプッシュ型データソースをモデル化するために使います。
Observableでモデル化したプッシュ型のデータソースの評価を実行します。
この評価を後処理可能なオブジェクトを返します。
Observableにはmap/filterといった馴染みのメソッドが実装されるかもしれません。
map/filterはEcma262の提案に入ってないから注意!
Observableによって、プッシュ型データソースを使った手続きを簡単に書くことができます。
Observableはまだ提案中なので使いたい時には以下のライブラリを利用できます。
RxJSはObservableを提供してくれます。RxJSを使うとmap/filter以外にもいろんなOperatorを使うことができます。
export class App {
items: Observable<Array<string>>;
term = new Control();
constructor(private wikipediaService: WikipediaService) {
// subscribeしていないことがポイント
this.items = this.term.valueChanges
.debounceTime(500)
.distinctUntilChanged()
.switchMap(term => this.wikipediaService.search(term));
}
}
@Component({ selector: 'my-app', template: ` <div> <h2>Wikipedia Search</h2> <input type="text" [ngFormControl]="term"/> <ul> <li *ngFor="#item of items | async">{{item}}</li> </ul> </div> ` })
@pastelInc