1
\$\begingroup\$

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!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.