Yksi tapa tuoda asynkronisia operaatioita sovellukseesi on Observer-malli.
Käyttämämme Observer on synkroninen toteutus ja saatavilla
sendanor/typescript-repositoriosta. Se on puhdasta TypeScriptiä ja käännettynä toimii missä tahansa JavaScript-ympäristössä.
Ensin määrittelemme FooEvent-enumeroinnin, joka sisältää tapahtumien nimet:
export enum FooEvent {
UPDATED = 'foo:updated',
ERROR = 'foo:error'
}
Seuraavaksi määrittelemme rajapinnan FooDTO, joka kuvaa miltä backendista tulevan data transfer objectin
pitää näyttää:
export interface FooDTO {
readonly name : string;
readonly summary : string;
}
Tarvitsemme myös ajonaikaisen testin, jotta TypeScriptin käännösaikainen analyysi vastaa ajonaikaista tilannetta, kun DTO saadaan backendista.
Luodaan ajonaikainen testi:
function isFooDTO (value : any) : value is FooDTO {
return (
isObject(value)
&& isString(value?.name)
&& isString(value?.summary)
);
}
Voit saada
isObject()- jaisString()-funktiot Lodash-kirjastosta tai käyttää TypeScript-optimoituja testifunktioitamme, jotka on kirjoitettu Lodashin päälle ja ovat saatavilla osoitteessa sendanor/typescript/modules/lodash.
Aloitetaan luomalla yksinkertainen FooService-luokka:
export class FooService {
private _data : FooDTO | undefined;
private _observer : Observer<FooEvent>;
public constructor () {
this._data = undefined;
this._observer = new Observer<FooEvent>("FooService");
}
public getData (): FooDTO | undefined {}
public refreshData () : void {}
public on (
name: FooEvent,
callback: ObserverCallback<FooEvent>
): ObserverDestructor {}
}
foo.getData()-metodia voidaan kutsua mistä tahansa, jotta saat viimeisimmän DTO:n backendista.
Se palauttaa undefined, jos dataa ei ole saatu.
public getData (): FooDTO | undefined {
return this._data;
}
foo.refreshData() kapseloi asynkronisen HTTP-pyynnön ja muuntaa sen Observer-tapahtumamalliksi:
public refreshData () : void {
HttpService.get('/path/to').then((response) => {
if (!isFooDTO(response)) {
throw new TypeError('Not FooDTO: ' + JSON.stringify(response));
}
this._data = response.data;
this._observer.triggerEvent(FooEvent.CHANGED);
}).catch(err => {
this._observer.triggerEvent(FooEvent.ERROR, err);
});
}
foo.on(FooEvent, callback)-metodia voidaan käyttää tapahtumien kuunteluun foo-instanssien ulkopuolelta:
public on (name: FooEvent, callback: ObserverCallback<FooEvent>): ObserverDestructor {
return this._observer.listenEvent(name, callback);
}
Paluuarvona on destruktori, jota voi kutsua kuuntelijan irrottamiseksi.
Tässä koko luokka:
export class FooService {
private _data : FooDTO | undefined;
private _observer : Observer<FooEvent>;
public constructor () {
this._data = undefined;
this._observer = new Observer<FooEvent>("FooService");
}
public getData (): FooDTO | undefined {
return this._data;
}
public refreshData () : void {
HttpService.get('/path/to').then((response) => {
if (!isFooDTO(response)) {
throw new TypeError('Not FooDTO: ' + JSON.stringify(response));
}
this._data = response.data;
this._observer.triggerEvent(FooEvent.CHANGED);
}).catch(err => {
this._observer.triggerEvent(FooEvent.ERROR, err);
});
}
public on (
name: FooEvent,
callback: ObserverCallback<FooEvent>
): ObserverDestructor {
return this._observer.listenEvent(name, callback);
}
}
Nyt voimme luoda foo-instansseja:
const fooService = new Foo();
Kun instanssi on luotu, voimme alkaa kuunnella kiinnostavia tapahtumia:
const changeListener = fooService.on(FooEvent.CHANGED, () => {
const currentData = fooService.getData();
// ...update view
});
Esimerkiksi tässä voit testata, onko data undefined, ja luoda UI:hin latausspinnerin,
ja muuten päivittää näkymän.
Virheiden kuunteluun voimme kuunnella FooEvent.ERROR-tapahtumaa:
const errorListener = fooService.on(FooEvent.ERROR, (ev : FooEvent, error : any) => {
console.error(`Error: `, err);
});
Asynkronisen HTTP-pyynnön käynnistämiseksi datamallin hakemiseksi voit kutsua foo.refreshData():
fooService.refreshData();
Kun tapahtumia ei enää tarvitse kuunnella, kutsu destruktoreita:
changeListener();
errorListener();
Todellisessa sovelluksessa loisit todennäköisesti UI-komponentin tai väliin palveluluokan,
joka yhdistää nämä operaatiot uudelleenkäytettäväksi .start()- ja .stop()-rajapinnaksi.
Siinä kaikki. Toivottavasti tästä oli hyötyä!
Kommentit
Jack hello world!
Jaakko Just testing
Jaakko testing again
Jaakko Testing #3
Jaakko Hello world!