The following code is an extract of a small library I wrote. Full code is available here.
My question is about the following function. I added comments on each state. I'm wondering if I could do things easier, I've the feeling I'm using too many flatMap
:
/**
* request
*
* @param {string} method
* @param {RxRestItem|FormData|URLSearchParams|Body|Blob|undefined|Object} [body]
* @returns {Observable<RxRestItem|RxRestCollection>}
*/
request(method: string, body?: BodyParam): Observable<RxRestItem|RxRestCollection> {
let requestOptions = {
method: method,
headers: <Headers> this.requestHeaders,
//this is just a function that transforms a Body to json if needed
body: this.requestBodyHandler(body)
}
let request = new Request(this.URL + this.requestQueryParams, requestOptions);
//let's start from our Request object
return Observable.of(request)
//here, we call functions that can alter the request, think middlewares (see below for this method)
.flatMap(this.expandInterceptors(Config.requestInterceptors))
//now we have the final request, we do a `fetch` on it
.flatMap(request => fetch(request))
//Transform the response to a javascript object
.flatMap(body => Observable.fromPromise(this.responseBodyHandler(body)))
//Here the library transforms object to internal types
.map(e => {
if (!Array.isArray(e)) {
let item: RxRestItem
if (this instanceof RxRestItem) {
item = this
item.element = e
} else {
item = new RxRestItem(this.$route, e)
}
item.$fromServer = true
return item
}
return new RxRestCollection(this.$route, e.map(e => {
let item = new RxRestItem(this.$route, e)
item.$fromServer = true
return item
}))
})
//response middlewares
.flatMap(this.expandInterceptors(Config.responseInterceptors))
.catch(body => {
//this was a real error
if (!(body instanceof Response)) {
return Observable.throw(body)
}
//error interceptors
return Observable.of(body)
.flatMap(this.expandInterceptors(Config.errorInterceptors))
})
}
The middleware function feels complicated too, it takes an array of functions and apply them (like a reduce
). However, this interceptor function can return a Promise
, an Observable
or a changed value:
/**
* expandInterceptors
*
* @param {RequestInterceptor[]|ResponseInterceptor[]|ErrorInterceptor[]} interceptors
* @returns {Observable<any>} fn
*/
expandInterceptors(interceptors: RequestInterceptor[]|ResponseInterceptor[]|ErrorInterceptor[]) {
return function(origin: any): Observable<any> {
return (<any>interceptors).reduce(
(obs: Observable<any>, interceptor: any) =>
obs.concatMap(value => {
let result = interceptor(value)
if (result === undefined) {
return Observable.of(value)
}
if (result instanceof Observable || result instanceof Promise) {
return result
}
return Observable.of(result)
}),
Observable.of(origin)
)
}
}
Do you think that I'm doing things the right way? Can this code be simplified?
I'm new to reactive programming and still trying to understand some of it's core concepts!