Skip to content

An observable wrapper for XMLHttpRequest using Kefir

Notifications You must be signed in to change notification settings

calmm-js/karet.xhr

Repository files navigation

This library allows one to declare XMLHttpRequests, compose them, and observe them through Kefir properties. This makes it easy to implement many kinds of use cases ranging from just getting the response data to visualizing the progress of non-trivial compositions of ongoing upload and/or download requests and displaying potential errors.

Examples:

  • The Giphy CodeSandbox uses this library to do simple JSON GET requests.
  • The GitHub repository search CodeSandbox uses this library to do JSON GET requests and exercises much of the API of this library as an example.

npm version Build Status Code Coverage

The interface of this library consists of named exports. Typically one just imports the library as:

import * as XHR from 'karet.xhr'

Using this library, one declares observable XMLHttpRequests, composes them, and then observes the ongoing XHR using the accessors for the result, overall, download, and upload state.

If you just want to GET some JSON...

XHR.getJson returns an observable that emits the full response after the XHR has succeeded. In case the XHR produces an error or times out, the XHR is emitted as an error event. See XHR.perform for the options.

Note that this function is provided for simplistic usages where one does not need the full composability and observability advantages of this library.

For example:

I.seq(
  XHR.getJson(`https://api.github.com/search/users?q=polytypic`),
  R.map(L.get(L.query('html_url'))),
  log
)

XHRs are declared by specifying all the parameters that affect the execution of an XHR to XHR.perform, which then returns an observable property that can be subscribed to in order to perform the declared XHR.

XHR.perform creates an observable property that represents the state of an ongoing XMLHttpRequest. The request is started once the property is subscribed to and is automatically aborted in case the property is fully unsubscribed from before it has ended. See also XHR.performWith and XHR.performJson.

Only the url parameter is required and can be passed as a string. Other parameters have their XHR default values:

Parameter Default Explanation
method 'GET' HTTP request method to use.
user null User name for authentication.
password null Password for authentication.
headers null An array of [header, value] pairs, a plain object of {header: value} properties, a Map, or a Headers object mapping headers to values.
overrideMimeType undefined If specified overrides the MIME type provided by the server.
body null A body of data to be sent.
responseType '' Specifies type of response data.
timeout 0 Number of milliseconds or 0 for infinite.
withCredentials false Whether cross-site Access-Control should use credentials.

In addition to a plain object, the argument to XHR.perform is allowed to be an observable property or contain observable properties, in which case the property created by XHR.perform performs the XHR with the latest argument values.

Note that typically one does not explicitly subscribe to the property, but one rather computes a desired view of the property, such as a view of the succeeded response, and combines that further into some more interesting property.

WARNING: Setting responseType to 'json' is not supported by IE 11. This library implements a workaround by calling JSON.parse on the returned data in case setting responseType to 'json' fails. In case the response does not parse, then XHR.response returns null.

XHR.performJson is shorthand for XHR.performWith({responseType: 'json', headers: {'Content-Type': 'application/json'}}).

XHR.performWith is a curried function that allows one to define a XHR.perform like function with default parameters. The defaults (first parameter) are merged with the overrides (second parameter). Headers are also merged. See XHR.perform for the parameters.

For example:

const get = XHR.performWith({responseType: 'json', timeout: 30 * 1000})
// ...
get(url)

Multiple XHRs can be composed together to appear and be treated simply as a single XHR.

XHR.ap implements a static land compatible ap function for composing succeeding XHRs. The XHRs are performed sequentially. See also XHR.apParallel and XHR.apply.

XHR.apParallel implements a static land compatible ap function for composing succeeding XHRs. The XHRs are performed in parallel. See also XHR.ap and XHR.apply.

XHR.chain implements a static land compatible chain function for composing succeeding XHRs.

XHR.map implements a static land compatible map function for composing succeeding XHRs.

XHR.of implements a static land compatible of function for composing succeeding XHRs.

XHR.apply maps the given XHRs through the given function. Unlike with XHR.ap, the XHRs are performed in parallel.

XHR.tap wraps the XHR so that the given action is called with the response after the XHR has succeeded. If the XHR does not succeed, the action will not be called.

Note that XHR.tap(action) is roughly equivalent to

XHR.map(response => {
  action(response)
  return response
})

XHR.template transforms a nested template of plain arrays and objects possibly containing XHRs into a XHR. The XHRs are performed in parallel.

Static Land compatible algebras can be used with other Static Land compatible libraries such as Partial Lenses to perform more complex XHRs.

For example:

I.seq(
  XHR.performJson(
    `https://api.github.com/search/repositories?q=user:calmm-js&sort=stars`
  ),
  XHR.map(
    L.collect([
      'items',
      L.limit(2, L.flat(L.when(R.has('description')))),
      L.pick({
        description: 'description',
        url: 'svn_url',
        issues: 'issues_url'
      })
    ])
  ),
  XHR.chain(
    L.traverse(
      XHR.Parallel,
      R.pipe(
        R.replace(/{.*}$/, ''),
        XHR.performJson,
        XHR.map(
          L.collect(
            L.limit(3, L.flat(L.pick({title: 'title', url: 'html_url'})))
          )
        )
      ),
      [L.elems, 'issues']
    )
  ),
  XHR.result,
  log
)

XHR.IdentityParallel is a static land compatible applicative that manipulates XHRs like XHR.Parallel or plain data.

XHR.IdentitySucceeded is a static land compatible monad that manipulates XHRs like XHR.Succeeded or plain data.

XHR.Parallel is a static land compatible applicative that allows one to compose parallel XHR requests. In case any XHR fails, the composed XHR produces the first failed XHR. In case all XHRs succeed, the composed XHR produces the combined XHR as the result.

XHR.Succeeded is a static land compatible monad comprised of the XHR.ap, XHR.chain, XHR.map, and XHR.of combinators that allows one to compose sequences of XHR requests that stop as soon as the first XHR does not succeed.

Ongoing XHRs can be observed both for their varying properties such as the number of bytes transferred and for their results.

XHR.hasFailed returns a possibly observable boolean property of an ongoing XHR that is true if its HTTP status does not indicate success or the download or the upload operation has errored or timed out.

XHR.hasSucceeded returns a possibly observable boolean property of an ongoing XHR that is true if the XHR is done, its HTTP status indicates success, and neither download or upload has errored or timed out.

XHR.result returns the response of a succeeded XHR. Note that XHR.response allows one to obtain the response before the XHR is done and even when the XHR has (partially) failed.

XHR.isStatusAvailable returns a possibly observable boolean property that tells whether HTTP status and response headers have been received and can be obtained. See also XHR.status, XHR.statusText, XHR.allResponseHeaders, and XHR.responseHeader.

XHR.isDone returns a possibly observable boolean property that tells whether the XHR operation is complete (whether success or failure). See also XHR.hasSucceeded.

XHR.isProgressing returns a possibly observable boolean property that tells whether the XHR operation has started, but has not yet ended.

XHR.hasErrored returns a possibly observable boolean property of an ongoing XHR that is true when either download or upload has errored.

XHR.hasTimedOut returns a possibly observable boolean property of an ongoing XHR that is true when either download or upload has timed out.

XHR.errors returns a possibly observable array of errors from download and upload. The array will contain 0 to 2 errors.

XHR.status returns a possibly observable property that emits the status after the HTTP status has been received. When called on a non-observable XHR, readyState must be 2 or an Error will be thrown.

XHR.statusIsHttpSuccess(xhr) is shorthand for XHR.isHttpSuccess(XHR.status(xhr)). Note that HTTP status is usually received before the download and upload phases have completed. See also XHR.hasSucceeded, XHR.status and XHR.isHttpSuccess.

XHR.statusText returns a possibly observable property of the statusText after the HTTP status has been received. When called on a non-observable XHR, readyState must be 2 or an Error will be thrown.

XHR.loaded returns a possibly observable property of the sum of downloaded and uploaded bytes.

XHR.loaded returns a possibly observable property of the sum of total download and total upload bytes.

XHR.allResponseHeaders returns a possibly observable property that emits the value of getAllResponseHeaders() after the HTTP headers have been received. When called on a non-observable XHR, its readyState must be 2 or an Error will be thrown.

XHR.responseHeader returns a possibly observable property that emits the value of getResponseHeader(header) for specified header after the HTTP headers have been received. When called on a non-observable XHR, its readyState must be 2 or an Error will be thrown.

XHR.response returns a possibly observable property that emits the response after the download operation of the XHR has completed. When called on a non-observable XHR, the download operation must be completed or an Error will be thrown. See also XHR.result, and XHR.responseText.

XHR.responseText returns a possibly observable property of the responseText property of an ongoing XHR. XHR.responseText is for observing the received response data before the data has been completely received. See also XHR.response.

XHR.responseXML returns a possibly observable property of the responseXML property after the XHR has completed. When called on a non-observable XHR, the download operation must be completed or an Error will be thrown. See also XHR.response.

XHR.responseURL returns a possibly observable property of the responseURL property after the HTTP headers have been received. When called on a non-observable XHR, its readyState must be 2 or an Error will be thrown.

XHR.responseType returns a possibly observable property of the responseType of an ongoing XHR.

XHR.timeout returns a possibly observable property of the timeout property of an ongoing XHR.

XHR.withCredentials returns a possibly observable property of the withCredentials property of an ongoing XHR.

XHR.readyState returns a possibly observable property of the readyState of an ongoing XHR.

XHR.downError returns a possibly observable property of the error property of an errored XHR.

XHR.downHasEnded returns a possibly observable boolean property that tells whether the download operation of an ongoing XHR has ended.

XHR.downHasErrored returns a possibly observable boolean property that tells whether the download operation of an ongoing XHR has errored.

XHR.downHasStarted returns a possibly observable boolean property that tells whether the download operation of an ongoing XHR has started.

XHR.downHasCompleted returns a possibly observable boolean property that tells whether the download operation of an ongoing XHR has been completed successfully. Note that this does not take into account the HTTP response status, see XHR.status and XHR.isHttpSuccess.

XHR.downHasTimedOut returns a possibly observable boolean property that tells whether the download operation of an ongoing XHR has timed out.

XHR.downIsProgressing returns a possibly observable boolean property that tells whether the download operation of an ongoing XHR is progressing.

XHR.downLoaded returns a possibly observable property of the loaded property of an ongoing XHR.

XHR.downTotal returns a possibly observable property of the total property of an ongoing XHR.

XHR.upError returns a possibly observable property of the error property of an errored XHR.

XHR.upHasEnded returns a possibly observable boolean property that tells whether the upload operation of an ongoing XHR has ended.

XHR.upHasErrored returns a possibly observable boolean property that tells whether the upload operation of an ongoing XHR has errored.

XHR.upHasStarted returns a possibly observable boolean property that tells whether the upload operation of an ongoing XHR has started.

XHR.upHasCompleted returns a possibly observable boolean property that tells whether the upload operation of an ongoing XHR has completed successfully. Note that this does not take into account the HTTP response status, see XHR.status and XHR.isHttpSuccess.

XHR.upHasTimedOut returns a possibly observable boolean property that tells whether the upload operation of an ongoing XHR has timed out.

XHR.upIsProgressing returns a possibly observable boolean property that tells whether the upload operation of an ongoing XHR is progressing.

XHR.upLoaded returns a possibly observable property of the loaded property of an ongoing XHR.

XHR.upTotal returns a possibly observable property of the total property of an ongoing XHR.

XHR.isHttpSuccess returns a possibly observable property of whether the given numeric property is in the range 2xx of HTTP success codes. See also XHR.statusIsHttpSuccess.

XHR.isXHR returns a possibly observable boolean property that tells whether the given value is a XHR.