Skip to content

Commit

Permalink
Merge 485f15e into af27f56
Browse files Browse the repository at this point in the history
  • Loading branch information
tusharmath committed Apr 14, 2016
2 parents af27f56 + 485f15e commit b85e139
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 60 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
Expand Up @@ -13,6 +13,10 @@ before_script:
- npm prune
after_success:
- nyc npm test && nyc report --reporter=text-lcov | coveralls
- npm run doc
- git add README.md
- git commit -m 'docs(README): update via jsdocs\n\n[ci skip]\n'
- git push origin $TRAVIS_BRANCH
- npm run semantic-release
branches:
except:
Expand Down
45 changes: 45 additions & 0 deletions README.hbs
@@ -0,0 +1,45 @@
# react-announce-fetch
[![Build Status][travis-svg]][travis]
[![npm][npm-svg]][npm]
[![semantic-release][semantic-release-svg]][semantic-release]
[![Coverage Status][coverage-svg]][coverage]

[travis-svg]: https://travis-ci.org/tusharmath/react-announce-fetch.svg?branch=master
[travis]: https://travis-ci.org/tusharmath/react-announce-fetch
[semantic-release-svg]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
[semantic-release]: https://github.com/semantic-release/semantic-release
[coverage-svg]: https://coveralls.io/repos/github/tusharmath/react-announce-fetch/badge.svg?branch=master
[coverage]: https://coveralls.io/github/tusharmath/react-announce-fetch?branch=master
[npm-svg]: https://img.shields.io/npm/v/react-announce-fetch.svg
[npm]: https://www.npmjs.com/package/react-announce-fetch


An HTTP based `reactive` data store. Essentially it takes in an input stream which represents the **HTTP Request Stream** (an Rx.Observable), and returns a **Response stream** (an Rx.Observable).

### Installation
```
npm install react-announce-fetch --save
```

### Example

```javascript
import Rx from 'rx'

import {create, toJSON} from 'react-announce-fetch'
import {connect} from 'react-announce-connect'
import {Component} from 'react'

const users = create(Rx.Observable.just(['/api/users', {method: 'GET'}]))

@connect(toJSON(users).pluck('json'))
UsersList extends Component {
render () {
const users = this.state.users
return <ul>{users.map(x => <li>{x.name}</li>)}</ul>
}
}

```

{{>main}}
156 changes: 103 additions & 53 deletions README.md
Expand Up @@ -14,91 +14,141 @@
[npm]: https://www.npmjs.com/package/react-announce-fetch


An HTTP based `reactive` data store. Essentially it takes in an input stream which represents the **HTTP Request Stream** (an Rx.Observable), and returns a **Communication Bus** (an Rx.Subject). Communication bus is how one can `send` as well as `receive` different kinds of events.
An HTTP based `reactive` data store. Essentially it takes in an input stream which represents the **HTTP Request Stream** (an Rx.Observable), and returns a **Response stream** (an Rx.Observable).

### Installation
```
npm install react-announce-fetch --save
```

### Usage

### Example

```javascript
import Rx from 'rx'

import {create, reload} from 'react-announce-fetch'
import {asStream} from 'react-announce'
import {create, toJSON} from 'react-announce-fetch'
import {connect} from 'react-announce-connect'
import {Component} from 'react'

const users = create(Rx.Observable.just(['/api/users', {method: 'GET'}]))

// Using @asStream binds the store to the components lifecycle events.
@asStream(users)
@connect(toJSON(users).pluck('json'))
UsersList extends Component {
render () {
return <div>Hi!</div>
const users = this.state.users
return <ul>{users.map(x => <li>{x.name}</li>)}</ul>
}
}

users.subscribe(x => console.log(x))
```

## Functions

<dl>
<dt><a href="#create">create(request, [fetch])</a> ⇒ <code><a href="#external_Observable">Observable</a></code></dt>
<dd><p>Takes in <code>request</code> parameters that will be used to call the <a href="#external_fetch">fetch</a> function.
The request params is essentially the arguments to the <a href="#external_fetch">fetch</a> function — <code>[url, options]</code>.
Internally the request observable is used as follows:</p>
<pre><code class="language-javascript">request.flatMap(params =&gt; fetch.apply(null, params))
</code></pre>
<h5 id="performance-">Performance:</h5>
<p>Though you can simply use the code above this module a couple of extra things to optimize interms of making HTTP requests.</p>
<ol>
<li><p>API requests are only made once per notification on the request stream, irrespective of how many subscribers have subscribed to it.
Once a response is received it is shared by all its subscribers.</p>
</li>
<li><p>No API requests must be made until there is at least one subscriber on the response stream.
Once there is a subscriber only one request should be made with the latest value on the request stream.</p>
</li>
<li><p>In case a subscriber disposes and resubscribes later in time, it should get the most recent response.</p>
</li>
</ol>
</dd>
<dt><a href="#toJSON">toJSON(response)</a> ⇒ <code><a href="#external_Observable">Observable</a></code></dt>
<dd><p>Create a <a href="#external_Response">Response</a> stream where the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Body/json">json()</a> method is already called upon.
It makes sure that json parsing is only done once and the result is shared amongst everyone.</p>
</dd>
</dl>

## External

<dl>
<dt><a href="#external_Observable">Observable</a></dt>
<dd><p>An observable is an interface that provides a generalized mechanism for push-based notification,
also known as observer design pattern.</p>
</dd>
<dt><a href="#external_Response">Response</a></dt>
<dd><p>Represents the response to a request in the <a href="#external_fetch">fetch</a> API.</p>
</dd>
<dt><a href="#external_fetch">fetch</a></dt>
<dd><p><code>window.fetch</code> is an easier way to make web requests and handle responses than using <code>XMLHttpRequest</code>.
It returns a promise and is easily consumed by <a href="https://github.com/Reactive-Extensions/RxJS">Rx</a>
using the <a href="https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/selectmany.md">flatMap</a> operator.</p>
</dd>
</dl>

<a name="create"></a>

## create(request, [fetch]) ⇒ <code>[Observable](#external_Observable)</code>
Takes in `request` parameters that will be used to call the [fetch](#external_fetch) function.
The request params is essentially the arguments to the [fetch](#external_fetch) function — `[url, options]`.
Internally the request observable is used as follows:
```js
request.flatMap(params => fetch.apply(null, params))
```
##### Performance:
Though you can simply use the code above this module a couple of extra things to optimize interms of making HTTP requests.

### How does it work?
[asStream]: https://github.com/tusharmath/react-announce#asstream
1. API requests are only made once per notification on the request stream, irrespective of how many subscribers have subscribed to it.
Once a response is received it is shared by all its subscribers.

It basically makes an HTTP request if one of the components that it is listening to (for lifecycle events) mounts. Once the response is received it is not going to make anymore requests, no matter how many components its linked to via **@asStream()** until, the request stream fires another *distinct* new value.
2. No API requests must be made until there is at least one subscriber on the response stream.
Once there is a subscriber only one request should be made with the latest value on the request stream.

Also, if none of the components are in mounted state and the request stream keeps firing with different values, no HTTP requests are going to be made, UNTILL one of the components mounts. In which case, the params of the last request would be used to make the HTTP request.
3. In case a subscriber disposes and resubscribes later in time, it should get the most recent response.

**Kind**: global function
**Returns**: <code>[Observable](#external_Observable)</code> - Emits the Response Object that returned by [fetch](#external_fetch)

## API
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| request | <code>[Observable](#external_Observable)</code> | | Request stream that emits params for the [fetch](#external_fetch) function as an array. |
| [fetch] | <code>function</code> | <code>window.fetch.bind(window)</code> | Optional custom fetch method |

### Events
The following two events are fired on the store —
- `REQUEST`: Fired as soon as the request is initiated.
- `RESPONSE`: Fired as soon as the response in completely received.
<a name="toJSON"></a>

```javascript
const users = create(Rx.Observable.just(['/api/users']))
## toJSON(response) ⇒ <code>[Observable](#external_Observable)</code>
Create a [Response](#external_Response) stream where the [json()](https://developer.mozilla.org/en-US/docs/Web/API/Body/json) method is already called upon.
It makes sure that json parsing is only done once and the result is shared amongst everyone.

users.filter(x => x.event === 'REQUEST').pluck('args')
users.filter(x => x.event === 'RESPONSE').pluck('args')
```
**Kind**: global function
**Returns**: <code>[Observable](#external_Observable)</code> - Maps [Response](#external_Response) to a JS object containing `json` and the original `response`.

### create(observable)
`store.create()` takes in one parameter — an `observable` which basically is the request stream that emits notifications in the of following schema format —
| Param | Type | Description |
| --- | --- | --- |
| response | <code>[Observable](#external_Observable)</code> | Response stream that is returned by [create](#create) |

**Sample Schema for Request Stream**
```javascript
[
url: `/api/users`,
{
method: `POST` // Defaults to get
body: JSON.stringify({name: 'Godzilla!', age: 390})
}
]
```
### reload(store)
`reload()` Forcefully refreshes the store. By default requests are only made when the request stream fires an event.
<a name="external_Observable"></a>

```javascript
import {reload} from 'react-announce-fetch'
reload(store)
```
## Observable
An observable is an interface that provides a generalized mechanism for push-based notification,
also known as observer design pattern.

### toJSON(store)
`toJSON()` is a simple utility method that simply exposes the store as a stream of JSON responses.
**Kind**: global external
**See**: [RxJS Observable](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md)
<a name="external_Response"></a>

```javascript
import {toJSON} from 'react-announce-fetch'
toJSON(store).subscribe(x => console.log(x))
```
### isLoading(store0, store1, store...)
`isLoading()` exposes a stream with a `Boolean` value representing if a HTTP request is completed or not. It can take any number of stores as an argument and dispatches `true` if any one request is pending and `false` when not.
## Response
Represents the response to a request in the [fetch](#external_fetch) API.

```javascript
import {isLoading} from 'react-announce-fetch'
isLoading(store0, store1, store2).subscribe(x => console.log(x))
```
**Kind**: global external
**See**: [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response)
<a name="external_fetch"></a>

## fetch
`window.fetch` is an easier way to make web requests and handle responses than using `XMLHttpRequest`.
It returns a promise and is easily consumed by [Rx](https://github.com/Reactive-Extensions/RxJS)
using the [flatMap](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/selectmany.md) operator.

**Kind**: global external
**See**: [fetch polyfill](https://github.com/github/fetch)
50 changes: 50 additions & 0 deletions index.js
Expand Up @@ -5,9 +5,59 @@

const create = require('./src/main').create

/**
* Takes in `request` parameters that will be used to call the {@link external:fetch} function.
* The request params is essentially the arguments to the {@link external:fetch} function — `[url, options]`.
* Internally the request observable is used as follows:
* ```js
* request.flatMap(params => fetch.apply(null, params))
* ```
* ##### Performance:
* Though you can simply use the code above this module a couple of extra things to optimize interms of making HTTP requests.
*
* 1. API requests are only made once per notification on the request stream, irrespective of how many subscribers have subscribed to it.
* Once a response is received it is shared by all its subscribers.
*
* 2. No API requests must be made until there is at least one subscriber on the response stream.
* Once there is a subscriber only one request should be made with the latest value on the request stream.
*
* 3. In case a subscriber disposes and resubscribes later in time, it should get the most recent response.
*
* @param {external:Observable} request - Request stream that emits params for the {@link external:fetch} function as an array.
* @param {function} [fetch=window.fetch.bind(window)] - Optional custom fetch method
* @return {external:Observable} Emits the Response Object that returned by {@link external:fetch}
*/
exports.create = (request, fetch) => create(
request,
fetch || window.fetch.bind(window)
)

/**
* Create a {@link external:Response} stream where the {@link https://developer.mozilla.org/en-US/docs/Web/API/Body/json json()} method is already called upon.
* It makes sure that json parsing is only done once and the result is shared amongst everyone.
* @function
* @param {external:Observable} response - Response stream that is returned by {@link create}
* @return {external:Observable} Maps {@link external:Response} to a JS object containing `json` and the original `response`.
*/
exports.toJSON = require('./src/toJSON')

/**
* An observable is an interface that provides a generalized mechanism for push-based notification,
* also known as observer design pattern.
* @external Observable
* @see {@link https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md RxJS Observable}
*/

/**
* Represents the response to a request in the {@link external:fetch} API.
* @external Response
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Response Response}
*/

/**
* `window.fetch` is an easier way to make web requests and handle responses than using `XMLHttpRequest`.
* It returns a promise and is easily consumed by {@link https://github.com/Reactive-Extensions/RxJS Rx}
* using the {@link https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/selectmany.md flatMap} operator.
* @external fetch
* @see {@link https://github.com/github/fetch fetch polyfill}
*/
4 changes: 3 additions & 1 deletion package.json
Expand Up @@ -4,7 +4,8 @@
"main": "index.js",
"scripts": {
"test": "node_modules/ava/cli.js --verbose",
"semantic-release": "semantic-release pre && npm publish && semantic-release post"
"semantic-release": "semantic-release pre && npm publish && semantic-release post",
"doc": "jsdoc2md index.js --template README.hbs > README.md"
},
"author": "tusharmath@gmail.com",
"license": "ISC",
Expand All @@ -17,6 +18,7 @@
"ava": "^0.14.0",
"coveralls": "^2.11.6",
"cz-conventional-changelog": "^1.1.5",
"jsdoc-to-markdown": "^1.3.3",
"nyc": "^6.0.0",
"semantic-release": "^4.3.5",
"sinon": "^1.17.3"
Expand Down
17 changes: 11 additions & 6 deletions src/main.js
Expand Up @@ -13,14 +13,14 @@ e.toObservable = fetch => function () {
return Rx.Observable.fromPromise(fetch.apply(null, args))
}

e.create = _.partial((toObservable, request, fetch) => {
// TODO: Move out of this module
e.replayLatestAsyncMap = (source, asyncMapper) => {
var refCount = 0
const fetchO = toObservable(fetch)
const _request = request.replay(null, 1)
const responses = _request
.flatMap(x => fetchO.apply(null, x))
const replaySource = source.replay(null, 1)
const responses = replaySource
.flatMap(asyncMapper)
.replay(null, 1)
_request.connect()
replaySource.connect()
return Rx.Observable.create(observer => {
if (++refCount > 0) {
var conn = responses.connect()
Expand All @@ -32,4 +32,9 @@ e.create = _.partial((toObservable, request, fetch) => {
}
}
})
}

e.create = _.partial((toObservable, request, fetch) => {
const fetchO = toObservable(fetch)
return e.replayLatestAsyncMap(request, x => fetchO.apply(null, x))
}, e.toObservable)

0 comments on commit b85e139

Please sign in to comment.