├── .DS_Store ├── .babelrc ├── .gitignore ├── README.md ├── build.js ├── dist ├── creator.js.js └── fromEvent.js.js ├── doc ├── reactive-programming.md ├── redux-observable-analysis.md ├── redux-observable.md └── zen.md ├── example ├── application │ ├── index.js │ └── store.js ├── instance_observables.js ├── observer.js ├── operators │ ├── filtering-operators.js │ ├── index.js │ ├── pluck-operators.js │ └── switchMap.js ├── redux-observable.js ├── static_observables.js ├── step1.js ├── step2.js └── step3_subject.js ├── index.html ├── lib ├── ActionsObservable.js ├── EPIC_END.js ├── combineEpics.js ├── createEpicMiddleware.js └── index.js ├── package.json └── webpack.config.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashhuang/dive-into-rxjs/3c4bf41f373b0d5b9fb1a46dba437d34e23fd90c/.DS_Store -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | presets: ['stage-0','react','es2015'] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | node_modules 3 | test.js 4 | .idea 5 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dive-into-rxjs 2 | 3 | > 对rxjs 5的使用编写demo示例并对rxJS的设计思想做简单分析 4 | 5 | > 由于rxjs 5已由版本4迁移,部分api的名字有些变更,需要开发者注意 6 | 7 | > [rxjs4 迁移至 rxjs5 文档](https://github.com/ReactiveX/rxjs/blob/master/MIGRATION.md) 8 | 9 | > 主程序示例代码在'''/example/*.js'''下面 10 | 11 | 12 | ## 参考文档 13 | [learn rxjs](https://www.learnrxjs.io/) 14 | [reactive io](http://reactivex.io/rxjs/) 15 | [reactive programming](https://en.wikipedia.org/wiki/Reactive_programming) 16 | 17 | ### 使用 18 | 19 | ```bash 20 | 21 | $ cnpm install 22 | $ npm start 23 | 24 | ``` 25 | ### rxjs核心理念分层 26 | 27 | ```bash 28 | 29 | Observable: 30 | proveider ==> 事件或者数据源invokable 31 | 32 | Observer: 33 | consumer ==> 监听observerable的信息分发 34 | 35 | Subscription: 36 | 执行Observable的数据 37 | 38 | Operators: 39 | 纯函数,以函数式风格处理数据,比如map, filter, concat, flatMap等操作符. 40 | 41 | Subject: 42 | 和eventEmitter基本相同,唯一的方式分发数据或者事件到多个Observer. 43 | 44 | Schedulers: 45 | 中心化处理并发,允许我们协调计算。比如e.g. setTimeout or requestAnimationFrame or others. 46 | ``` 47 | 48 | ### 核心思想 49 | 50 | 1. Observerable核心 51 | 52 | > Observable=>Observer: Subscribing to an Observable is analogous to calling a Function. 53 | 54 | ```bash 55 | 56 | Creating Observables 57 | Subscribing to Observables 58 | Executing the Observable 59 | Disposing Observables 60 | 61 | ``` 62 | 63 | ### why rxjs 64 | 65 | 1. purity: 采用纯函数加工数据 66 | 67 | ```js 68 | //老的写法 69 | var count = 0; 70 | var button = document.querySelector('button'); 71 | button.addEventListener('click', () => console.log(`Clicked ${++count} times`)); 72 | 73 | //对比 74 | var button = document.querySelector('button'); 75 | Rx.Observable.fromEvent(button, 'click') 76 | //https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/scan.md 77 | .scan(count => count + 1, 0) 78 | .subscribe(count => console.log(`Clicked ${count} times`)); 79 | 80 | ``` 81 | 82 | 2. flow: 通知机制的流式处理 83 | 84 | ```js 85 | // 以典型的button每秒点击为例。 采用rx提供的throttleTime操作符 86 | const button = document.querySelector('button'); 87 | Rx.Observable.fromEvent(button, 'click') 88 | .throttleTime(1000) 89 | .scan(count => count + 1, 0) 90 | .subscribe(count => console.log(`Clicked ${count} times`)); 91 | 92 | ``` 93 | 94 | **类似的operator** 95 | 96 | ```bash 97 | filter, delay, debounceTime, take, takeUntil, distinct, distinctUntilChanged etc. 98 | ``` 99 | 100 | 3. values: 数据处理 101 | 102 | ```js 103 | const button = document.querySelector('button'); 104 | Rx.Observable.fromEvent(button, 'click') 105 | .throttleTime(1000) 106 | .map(event => event.clientX) 107 | .scan((count, clientX) => count + clientX, 0) 108 | .subscribe(count => console.log(count)) 109 | ``` 110 | 111 | **类似的operator** 112 | 113 | ```bash 114 | pluck, pairwise, sample etc. 115 | ``` 116 | 117 | 118 | ## 核心概念分解 119 | 120 | ### Observable 121 | 122 | 123 | 124 | > rxJS的各种api命名和lodash很像 125 | 126 | > rxJS的通知执行机制python的generator很类似, 127 | 128 | > 在程序流上面能够很好的和PromiseA+库等保持兼容性 129 | 130 | 131 | ## 本项目持续更新中,欢迎提出PR 132 | 133 | [rxjs 5 github文档](https://github.com/ReactiveX/rxjs/tree/master/doc) 134 | 135 | ### Copyright 136 | 137 | Copyright (C) 2016 ~ [slashhuang](http://github.com/slashhuang) 627284482@qq.com 138 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | /* 2 | * webpack配置入口 3 | * @Author slashhuang 4 | * 17/5/4 5 | */ 6 | const path =require('path') 7 | const express = require('express') 8 | const webpack = require('webpack') 9 | const webpackDevMiddleware = require('webpack-dev-middleware') 10 | const webpackHotMiddleware = require('webpack-hot-middleware') 11 | const webpackConfig = require('./webpack.config') 12 | // integrate hot client 13 | const hotclient = ['webpack-hot-middleware/client?noInfo=true&reload=true'] 14 | const entry = webpackConfig.entry 15 | Object.keys(entry).forEach((name) => { 16 | const value = entry[name] 17 | if (Array.isArray(value)) { 18 | value.unshift(...hotclient) 19 | } else { 20 | entry[name] = [...hotclient, value] 21 | } 22 | }) 23 | 24 | const app = express() 25 | const compiler = webpack(webpackConfig) 26 | app.use(webpackDevMiddleware(compiler, { 27 | publicPath: '/dist/', 28 | stats: { 29 | colors: true, 30 | chunks: false 31 | } 32 | })) 33 | app.use(webpackHotMiddleware(compiler)) 34 | app.get('/',(req,res)=>{ 35 | res.sendFile(path.resolve(__dirname,'./index.html')) 36 | }) 37 | // app.use(express.static(__dirname)) 38 | const port = process.env.PORT || 8000 39 | module.exports = app.listen(port, () => { 40 | console.log(`Server listening on http://localhost:${port}, Ctrl+C to stop`) 41 | }) 42 | -------------------------------------------------------------------------------- /doc/reactive-programming.md: -------------------------------------------------------------------------------- 1 | # reactive-programming 2 | 3 | - event-driven ==> react to events 4 | - scalable ==> react to load 5 | - resilient ==> react to failure 6 | - responsive ==> react to users 7 | 8 | dataflow and compositon of events 9 | 10 | 11 | 12 | 13 | 14 | 15 | ## event event-driven 16 | 17 | - events can be handled async,without blocking 18 | 19 | ## scalable 20 | 21 | - scale up : make use of parallelism in multi-core system 22 | 23 | - scale out : make use of multiple server nodes 24 | 25 | important for scalability: minisize shared mutable state 26 | import for scale out : location transparency, resilience 27 | 28 | ## resilient 29 | 30 | - loose coupling 31 | - string encapsulation of state 32 | - pervasive supervisor hierachy 33 | 34 | ## responsive 35 | 36 | - real-time 37 | 38 | 39 | ## example 40 | 41 | ```python 42 | class Counter extends ActionListener{ 43 | private var count = 0 44 | button.addActionListener(this) 45 | def actionPerformed(e:ActionEvent):Unit={ 46 | count+=1 47 | } 48 | } 49 | // problems 50 | // shared mutable state 51 | // can not be composed 52 | // leads quickly to 'callback hell' 53 | ``` 54 | ===> 55 | 1. events are first class 56 | 2. events are often represented as messages 57 | 3. handlers of events are also first-class 58 | 4. complex handlers can be composed from primitive ones 59 | 60 | 61 | 62 | ## 体系 63 | 1. functional reactive-programming 64 | 2. monads 65 | 3. abstracting over events ==> futures 66 | 4. observables 67 | 5. actors 68 | 6. handling falure==>supervisors 69 | 7. scale out ==>distributed actors 70 | 71 | 72 | 73 | 74 | ### observable 75 | 76 | what is it? 77 | how ist it different from an iterator? 78 | - push data to the client when data is ready 79 | - may be sync or async 80 | how is it different from the observer pattern? 81 | it's that ,plus 82 | signal end of data stream 83 | propagate error 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /doc/redux-observable-analysis.md: -------------------------------------------------------------------------------- 1 | ## redux-observable分析 2 | 3 | ### 1.几个核心概念 4 | 5 | - adapter 6 | 7 | ```js 8 | 9 | const adapter = { 10 | input: input$ => /* convert Observable to your preferred stream library */, 11 | output: output$ => /* convert your preferred stream back to an Observable */ 12 | }; 13 | 14 | ``` 15 | 16 | - Epic 17 | 18 | > function (action$: Observable, store: Store): Observable; 19 | 20 | ### 2. 分析一段demo code 21 | 22 | 23 | const PING = 'PING'; 24 | const PONG = 'PONG'; 25 | const ping = () => ({ type: PING }); 26 | const pingEpic = action$ => 27 | action$.ofType(PING) 28 | .delay(1000) // Asynchronously wait 1000ms then continue 29 | .mapTo({ type: PONG }); 30 | 31 | const pingReducer = (state = { isPinging: false }, action) => { 32 | switch (action.type) { 33 | case PING: 34 | return { isPinging: true }; 35 | 36 | case PONG: 37 | return { isPinging: false }; 38 | 39 | default: 40 | return state; 41 | } 42 | }; 43 | 44 | // components/App.js 45 | 46 | const { connect } = ReactRedux; 47 | 48 | let App = ({ isPinging, ping }) => ( 49 |
50 |

is pinging: {isPinging.toString()}

51 | 52 |
53 | ) 54 | ``` 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /doc/redux-observable.md: -------------------------------------------------------------------------------- 1 | ## redux-observable 2 | 3 | - actions in ===> actions out 4 | 5 | 6 | - compose ==> function in and function out 7 | 8 | ```js 9 | // 比如applyMiddleware逻辑,修改dispatch 10 | compose.apply(null,[a,b,c])(store.dispatch) ==> 11 | (store.dispatch) ==>a(b(c(dispatch)) 12 | // rxjs中的使用方式 13 | composeEnhancers( 14 | applyMiddleware( 15 | epicMiddleware, 16 | routerMiddleware(browserHistory) 17 | ) 18 | ) ===> (arguments)=>applyMiddleware( 19 | epicMiddleware, 20 | routerMiddleware(browserHistory) 21 | )(arguments) 22 | ``` 23 | 24 | 25 | 26 | - combineEpics ==> Merges all epics into a single one. 27 | 28 | ```js 29 | (...epics) => (...args) => 30 | merge( 31 | ...epics.map(epic => { 32 | const output$ = epic(...args); 33 | if (!output$) { 34 | throw new TypeError(`combineEpics: one of the provided Epics "${epic.name || ''}" does not return a stream. Double check you\'re not missing a return statement!`); 35 | } 36 | return output$; 37 | }) 38 | ); 39 | ``` 40 | 41 | - createEpicMiddleware ===> 42 | 43 | 44 | ```js 45 | 46 | export function createEpicMiddleware(epic, options = defaultOptions) { 47 | if (typeof epic !== 'function') { 48 | throw new TypeError('You must provide a root Epic to createEpicMiddleware'); 49 | } 50 | 51 | // even though we used default param, we need to merge the defaults 52 | // inside the options object as well in case they declare only some 53 | options = { ...defaultOptions, ...options }; 54 | const input$ = new Subject(); 55 | const action$ = options.adapter.input( 56 | new ActionsObservable(input$) 57 | ); 58 | const epic$ = new Subject(); 59 | let store; 60 | 61 | const epicMiddleware = _store => { 62 | store = _store; 63 | 64 | return next => { 65 | epic$ 66 | ::map(epic => { 67 | const output$ = ('dependencies' in options) 68 | ? epic(action$, store, options.dependencies) 69 | : epic(action$, store); 70 | 71 | if (!output$) { 72 | throw new TypeError(`Your root Epic "${epic.name || ''}" does not return a stream. Double check you\'re not missing a return statement!`); 73 | } 74 | 75 | return output$; 76 | }) 77 | ::switchMap(output$ => options.adapter.output(output$)) 78 | .subscribe(store.dispatch); 79 | 80 | // Setup initial root epic 81 | epic$.next(epic); 82 | 83 | return action => { 84 | const result = next(action); 85 | input$.next(action); 86 | return result; 87 | }; 88 | }; 89 | }; 90 | 91 | epicMiddleware.replaceEpic = epic => { 92 | // gives the previous root Epic a last chance 93 | // to do some clean up 94 | store.dispatch({ type: EPIC_END }); 95 | // switches to the new root Epic, synchronously terminating 96 | // the previous one 97 | epic$.next(epic); 98 | }; 99 | 100 | return epicMiddleware; 101 | } 102 | 103 | 104 | ``` 105 | -------------------------------------------------------------------------------- /doc/zen.md: -------------------------------------------------------------------------------- 1 | ## rxjs 概述 2 | 3 | > rxjs next ==> rxjs 5 4 | 5 | ## what is rxjs 6 | 7 | - reactive programming 8 | 9 | - treat events as collections 10 | 11 | - manipulate sets of events with operators 12 | 13 | ## Observables 14 | 15 | - streams of sets 16 | - of any number of things 17 | - over any amount of time 18 | - lazy. Observables will not generate values via an underlying 19 | producer util they're subscribed to. 20 | 21 | - can be "unsubscribed" from . this means the underlying 22 | producer can be told to stop and even tear down. 23 | 24 | 25 | ## Observables vs Promise 26 | 27 | - they have some similar semantics 28 | 29 | let sub = x.subscribe(valueFn,errorFn,completeFn) 30 | 31 | sub.unsubscribe() 32 | 33 | - create Observables is similar to create Promise 34 | 35 | ```js 36 | let p = new Promise((resolve,reject)=>{ 37 | doAsync((err,value)=>{ 38 | if(err){ 39 | reject(err); 40 | }else{ 41 | resolve(value) 42 | } 43 | }) 44 | }) 45 | ``` 46 | 47 | ```js 48 | let p = new Observable(observer=>{ 49 | doAsync((err,value)=>{ 50 | if(err){ 51 | observer.error(err); 52 | }else{ 53 | observer.next(value) 54 | observer.complete() 55 | } 56 | }) 57 | }) 58 | ``` 59 | 60 | 当执行 subscribe的时候,push notification ==> observer 61 | 62 | ### Observable creation helpers in rxjs 63 | 64 | ```js 65 | Observable.of(val1,val2) 66 | Observable.from(promise/iterable/observable) 67 | Observable.fromEvent(item,eventName) 68 | 69 | ``` 70 | 71 | Error handling in observable is similar to Promise 72 | 73 | ```js 74 | 75 | myProise.catch(error=>{ 76 | Promise.resolve(error) 77 | }) 78 | //rxjs 79 | myObservable.catch(error=>{ 80 | return Observable.of('okay') 81 | }) 82 | myObservable.finallly(error=>{ 83 | return 'done' 84 | }) 85 | 86 | ``` 87 | 88 | Observable can be 'retried' 89 | 90 | ```js 91 | Observable.retry(3) 92 | ``` 93 | 94 | ## what are 'operators' 95 | 96 | they're methods on observable that allow you to compose 97 | new Observables 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | ## 参考资料 108 | 109 | [rxjs google benlesh talk](https://www.youtube.com/watch?v=KOOT7BArVHQ) 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /example/application/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashhuang/dive-into-rxjs/3c4bf41f373b0d5b9fb1a46dba437d34e23fd90c/example/application/index.js -------------------------------------------------------------------------------- /example/application/store.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author slashhuang 3 | * 采用rxjs建立runtime的数据存储 4 | * 17/5/5 5 | */ 6 | 7 | import Rx from 'rxjs/Rx'; 8 | -------------------------------------------------------------------------------- /example/instance_observables.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author slashhuang 3 | * 17/5/9 4 | * https://github.com/ReactiveX/rxjs/blob/master/doc/observable.md 5 | * Observables are lazy Push collections of multiple values 6 | */ 7 | //observer 公共区域 8 | const Observer = { 9 | next: x => console.log('got value '+(Object.prototype.toString.call(x)) + x), 10 | error: err => console.error('something wrong occurred: ' + err), 11 | complete: () => console.log('done'), 12 | } 13 | /* 14 | * Combines multiple Observables to 15 | * create an Observable whose values are calculated from the latest values of 16 | * each of its input Observables. 17 | * 18 | * combineLatest(other: ObservableInput, project: function): Observable 19 | */ 20 | import{ Observable} from 'rxjs/Rx' 21 | const o_of1 = Observable.interval(1000) 22 | const o_of = Observable.timer(1000,1000) 23 | const o_combineLatest = o_of1.combineLatest(o_of,(f,o)=>(f+'--'+o)) 24 | const subsciber_c = o_combineLatest.subscribe(Observer) 25 | /* 26 | * Adds a tear down to be called during the unsubscribe() of this Subscription. 27 | */ 28 | const sub_0 = subsciber_c.add(()=>{console.log('tear down logic 0')}) 29 | const sub_1 = subsciber_c.add(()=>{console.log('tear down logic 1')}) 30 | const sub_2 = subsciber_c.add(()=>{console.log('tear down logic 2')}) 31 | subsciber_c.remove(sub_2) 32 | subsciber_c.unsubscribe() 33 | 34 | 35 | -------------------------------------------------------------------------------- /example/observer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author slashhuang 3 | * 17/5/4 4 | * 5 | * 6 | An interface for a consumer of push-based notifications 7 | delivered by an Observable. 8 | 9 | interface Observer { 10 | closed?: boolean; 11 | next: (value: T) => void; 12 | error: (err: any) => void; 13 | complete: () => void; 14 | } 15 | */ 16 | import Rx from 'rxjs/Rx'; 17 | // https://github.com/ReactiveX/rxjs/blob/master/doc/observer.md 18 | /* 19 | * An Observer is a consumer of values delivered by an Observable 20 | * Observers are simply a set of callbacks, 21 | * one for each type of notification delivered by the Observable: next, error, and complete. 22 | */ 23 | var subscriberObs = { 24 | next: x=>console.log('Next: ' + x), 25 | complete: ()=>console.log('Completed'), 26 | error: err=>console.log('Error: ' + err), 27 | } 28 | //直接call observer的instance 方法 29 | subscriberObs.next(42); 30 | subscriberObs.next(44); 31 | subscriberObs.error(43); 32 | subscriberObs.complete(); 33 | -------------------------------------------------------------------------------- /example/operators/filtering-operators.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author slashhuang 3 | * 17/5/5 4 | * filtering operators 5 | */ 6 | import Rx from 'rxjs'; 7 | //emit (1,2,3,4,5) 8 | const source = Rx.Observable.from([1,2,3,4,5]); 9 | //filter out non-even numbers 10 | const example = source.filter(num => num % 2 === 0); 11 | //output: "Even number: 2", "Even number: 4" 12 | const subscribe = example.subscribe(val => console.log(`subscribe Even number: ${val}`)); 13 | 14 | const subscribe1 = example.subscribe(val => console.log(`subscribe1 Even number: ${val}`)); 15 | 16 | -------------------------------------------------------------------------------- /example/operators/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author slashhuang 3 | * 17/5/4 4 | * rxjs操作符 5 | https://github.com/ReactiveX/rxjs/blob/master/doc/operators.md 6 | */ 7 | 8 | /* 9 | * filtering observables 10 | */ 11 | require('./filtering-operators') 12 | /* 13 | * switchMap operators 14 | */ 15 | require('./switchMap') 16 | /** 17 | * pluck operator 18 | */ 19 | require('./pluck-operators'); -------------------------------------------------------------------------------- /example/operators/pluck-operators.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author slashhuang 3 | * 17/5/4 4 | */ 5 | import Rx from 'rxjs/Rx'; 6 | 7 | //基本的transform操作 map pluck select ===> MapObservable 8 | // map or select (the two are synonymous) 9 | console.log(Rx); 10 | const originSource = Rx.Observable 11 | .from([ 12 | { value: 0 }, 13 | { value: 1 }, 14 | { value: 2 } 15 | ]) 16 | //这两者等价 17 | const pluckObs = originSource.pluck('value'); 18 | const Subscribers = [ 19 | val=>console.log('next with '+val), 20 | err=>console.log('err'+err), 21 | ()=>console.log('completed') 22 | ] 23 | //打印0,1,2 24 | pluckObs.subscribe.apply(pluckObs,Subscribers) 25 | -------------------------------------------------------------------------------- /example/operators/switchMap.js: -------------------------------------------------------------------------------- 1 | import Rx from 'rxjs/Rx'; 2 | 3 | var clicks = Rx.Observable.fromEvent(document, 'click'); 4 | var result = clicks.switchMap((ev) => Rx.Observable.interval(1000)); 5 | result.subscribe(x => console.log(x)); -------------------------------------------------------------------------------- /example/redux-observable.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 2017/5/12 3 | * redux observable 4 | */ 5 | import 'rxjs'; 6 | import { Observable } from 'rxjs/Observable' 7 | import { 8 | createEpicMiddleware , 9 | ActionsObservable , 10 | combineEpics, 11 | EPIC_END 12 | } from '../lib/' 13 | 14 | import { 15 | createStore, 16 | combineReducers, 17 | applyMiddleware 18 | } from 'redux' 19 | const epic1 = action$ =>{ 20 | return action$.ofType('epic1') 21 | .delay(2000) 22 | .mergeMap(() => Observable.merge( 23 | Observable.of({ type: 'not good'}), 24 | Observable.timer(2000) 25 | .mapTo({type:'epic2'}) 26 | )); 27 | } 28 | const epic2 = action$ => 29 | action$.ofType('epic2') 30 | .delay(1000) 31 | .mapTo({ type: 'epic3' }); 32 | 33 | const epicMiddleware = createEpicMiddleware(combineEpics(epic1,epic2)) 34 | const logMiddleware = store=>next=>action=>{ 35 | console.log('logger---',action) 36 | next(action) 37 | } 38 | const rootReducer = (state={init:'init'}, action) => { 39 | debugger; 40 | switch (action.type) { 41 | case 'epic3': 42 | case 'epic2': 43 | case 'epic1': 44 | return action ; 45 | default: 46 | return state; 47 | } 48 | }; 49 | const store = createStore(rootReducer,applyMiddleware(epicMiddleware,logMiddleware)) 50 | 51 | store.dispatch({type:'epic1'}) 52 | console.log(store.getState()) 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /example/static_observables.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author slashhuang 3 | * 17/5/4 4 | * https://github.com/ReactiveX/rxjs/blob/master/doc/observable.md 5 | * Observables are lazy Push collections of multiple values 6 | */ 7 | import { Observable } from 'rxjs/Rx'; 8 | //observer 公共区域 9 | const Observer = { 10 | next: x => console.log('got value '+(Object.prototype.toString.call(x)) + x), 11 | error: err => console.error('something wrong occurred: ' + err), 12 | complete: () => console.log('done'), 13 | } 14 | /* 15 | * Creates an Observable that emits some values you specify as arguments 16 | * of(values: ...T, scheduler: Scheduler): 17 | */ 18 | const o_concat = Observable.of([1,2,3]) 19 | o_concat.subscribe(Observer); 20 | /* 21 | * Creates an Observable that emits no items to the Observer 22 | * and immediately emits an error notification. 23 | * throw(error: any, scheduler: Scheduler) 24 | */ 25 | const o_throw = Observable.throw('error') 26 | o_throw.subscribe(Observer) 27 | /* 28 | * Creates an Observable that emits no items to the Observer 29 | * and immediately emits a complete notification. 30 | * empty(scheduler: Scheduler) 31 | */ 32 | const o_empty = Observable.empty() 33 | o_empty.subscribe(Observer) 34 | 35 | /* 36 | * Creates an Observable that emits a sequence of numbers within a specified range. 37 | * range(start: number, count: number, scheduler: Scheduler) 38 | */ 39 | 40 | const o_range = Observable.range(3,10) 41 | o_range.subscribe(Observer) 42 | /* 43 | * Creates an Observable that emits sequential 44 | * numbers every specified interval of time, on a specified IScheduler. 45 | * 46 | * interval(period: number, scheduler: Scheduler) 47 | */ 48 | const o_interval = Observable.interval(1000) 49 | const subscribed = o_interval.subscribe(Observer) 50 | setTimeout(()=>subscribed.unsubscribe(),3000) 51 | /* 52 | * create the Observable only when the Observer subscribes, 53 | * and create a fresh Observable for each Observer 54 | * 55 | * return Observable 56 | * 57 | * defer(observableFactory: function(): SubscribableOrPromise): 58 | */ 59 | const o_defer = Observable.defer(()=>{ 60 | if(Math.random()>0.5){ 61 | return Observable.of('---') 62 | }else{ 63 | return Observable.range(1,2) 64 | } 65 | }) 66 | o_defer.subscribe(Observer) 67 | /* 68 | * Creates a new Observable, that will execute the specified 69 | * function when an Observer subscribes to it. 70 | * 71 | * create(onSubscription: function(observer: Observer): TeardownLogic) 72 | */ 73 | const o_create = Observable.create(observer=>{ 74 | observer.next('create--') 75 | observer.error('error for create') 76 | //error和complete只有一个会成为最终态 77 | // observer.complete() 78 | }) 79 | o_create.subscribe(Observer) 80 | /* 81 | * Creates an output Observable which concurrently emits all values from every given input Observable 82 | * 83 | * merge(observables: ...ObservableInput, concurrent: number, scheduler: Scheduler) 84 | * 85 | * 86 | * timer(initialDelay: number | Date, period: number, scheduler: Scheduler): 87 | */ 88 | const clicks = Observable.fromEvent(document, 'click'); 89 | const timer = Observable.interval(1000); 90 | const clicksOrTimer = Observable.merge(clicks, timer); 91 | // clicksOrTimer.subscribe(Observer); 92 | /* 93 | * Combines multiple Observables to create an Observable 94 | * whose values are calculated from the latest values of each of 95 | * its input Observables. 96 | * 97 | * combineLatest(observable1: ObservableInput, observable2: ObservableInput, project: function, scheduler: Scheduler) 98 | */ 99 | const observablesArr = [1,2,3].map(n=>Observable.timer(n*2000, 1000)) 100 | const o_combineLatest = Observable.combineLatest(observablesArr); 101 | // o_combineLatest.subscribe(Observer); 102 | /* 103 | * Creates an Observable from an Array, an array-like object, 104 | * a Promise, an iterable object, or an Observable-like object. 105 | * 106 | * from(ish: ObservableInput, scheduler: Scheduler) 107 | */ 108 | const o_from = Observable.from([1,2,'fuck']); 109 | o_from.subscribe(Observer); 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /example/step1.js: -------------------------------------------------------------------------------- 1 | import Rx from 'rxjs'; 2 | /** 3 | * Observable 4 | * observer 5 | * subject 6 | * subscription 7 | * operator 8 | */ 9 | // data、stream producer 10 | const observable1 = Rx.Observable.of([1,2]); 11 | const observable2 = Rx.Observable.from([1,2,3,4,5]); 12 | /** 13 | * consumer 14 | * iterable pattern 可遍历模式 15 | * array/yield/symbol 16 | * next(1) next(2) 17 | * [1,2,3,4,5] 18 | */ 19 | const observer = { 20 | next: (val) => { 21 | console.log(val); 22 | }, 23 | complete: () => { 24 | 25 | }, 26 | error: (Err) => { 27 | console.error(Err); 28 | }, 29 | }; 30 | // subscribe 链接observable和observer 31 | observable2.subscribe(observer) 32 | observable1.subscribe(observer); 33 | -------------------------------------------------------------------------------- /example/step2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * operator + Subject 3 | */ 4 | import Rx from 'rxjs'; 5 | const observable1 = Rx.Observable.from([1,2,3,4]); 6 | const filterObservable1 = observable1.filter((value) => { 7 | return value%2 === 0; 8 | }); 9 | const observer1 = { 10 | next: (val) => { 11 | console.log(val); 12 | }, 13 | complete: () => { 14 | console.log('completed') 15 | }, 16 | error: () => { 17 | console.error('completed') 18 | }, 19 | }; 20 | filterObservable1.subscribe(observer1); 21 | /** 22 | * operator overview --- 23 | * creation, 24 | * transformation, 25 | * filtering, 26 | * combination, 27 | * multicasting, 28 | * error handling, 29 | * utility 30 | */ 31 | // creation 32 | const clickObservable = Rx.Observable.fromEvent(document, 'click'); 33 | clickObservable.subscribe(observer1) 34 | // transformation 35 | const interval = Rx.Observable.interval(1000); 36 | const buffered = interval.buffer(clickObservable); 37 | buffered.subscribe({next: (val) => console.log(val)}); 38 | // filtering 39 | const result = clickObservable.debounce(() => Rx.Observable.interval(1000)); 40 | result.subscribe(x => console.log(x)); 41 | // combination 42 | let age$ = Rx.Observable.of(27, 25, 29, 30); 43 | let name$ = Rx.Observable.of('Foo', 'Bar', 'Beer'); 44 | let isDev$ = Rx.Observable.of(true); 45 | Rx.Observable 46 | .zip(age$, 47 | name$, 48 | isDev$) 49 | .subscribe(x => console.log(x)); 50 | // multicasting 51 | const source = Rx.Observable.interval(1000); 52 | const example = source.publish(); 53 | //do nothing until connect() is called 54 | const subscribe = example.subscribe(val => console.log(`Subscriber One: ${val}`)); 55 | const subscribeTwo = example.subscribe(val => console.log(`Subscriber Two: ${val}`)); 56 | setTimeout(() => { 57 | example.connect(); 58 | },5000); -------------------------------------------------------------------------------- /example/step3_subject.js: -------------------------------------------------------------------------------- 1 | // subscribe + publish pattern 2 | // observer pattern 3 | import { Subject } from 'rxjs'; 4 | const sub1 = new Subject(); 5 | sub1.subscribe((val) => { 6 | console.log(val); 7 | }); 8 | sub1.subscribe((val) => { 9 | console.log('hello' + val); 10 | }); 11 | // sub1 内部 [fn1, fn2] 12 | sub1.next(1); 13 | sub1.next(2); 14 | // observable ==value==> subject 15 | // observer1 observer2 observer3 16 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dive-into-rxJS 5 | 32 | 33 | 34 |
35 | Observables versus Observer 36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /lib/ActionsObservable.js: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs/Observable'; 2 | import { of } from 'rxjs/observable/of'; 3 | import { from } from 'rxjs/observable/from'; 4 | import { filter } from 'rxjs/operator/filter'; 5 | 6 | export class ActionsObservable extends Observable { 7 | static of(...actions) { 8 | return new this(of(...actions)); 9 | } 10 | 11 | static from(actions, scheduler) { 12 | return new this(from(actions, scheduler)); 13 | } 14 | 15 | constructor(actionsSubject) { 16 | super(); 17 | this.source = actionsSubject; 18 | } 19 | 20 | lift(operator) { 21 | const observable = new ActionsObservable(this); 22 | observable.operator = operator; 23 | return observable; 24 | } 25 | 26 | ofType(...keys) { 27 | return this::filter(({ type }) => { 28 | const len = keys.length; 29 | if (len === 1) { 30 | return type === keys[0]; 31 | } else { 32 | for (let i = 0; i < len; i++) { 33 | if (keys[i] === type) { 34 | return true; 35 | } 36 | } 37 | } 38 | return false; 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/EPIC_END.js: -------------------------------------------------------------------------------- 1 | export const EPIC_END = '@@redux-observable/EPIC_END'; 2 | -------------------------------------------------------------------------------- /lib/combineEpics.js: -------------------------------------------------------------------------------- 1 | import { merge } from 'rxjs/observable/merge'; 2 | 3 | /** 4 | Merges all epics into a single one. 5 | */ 6 | export const combineEpics = (...epics) => (...args) => 7 | merge( 8 | ...epics.map(epic => { 9 | const output$ = epic(...args); 10 | if (!output$) { 11 | throw new TypeError(`combineEpics: one of the provided Epics "${epic.name || ''}" does not return a stream. Double check you\'re not missing a return statement!`); 12 | } 13 | return output$; 14 | }) 15 | ); 16 | -------------------------------------------------------------------------------- /lib/createEpicMiddleware.js: -------------------------------------------------------------------------------- 1 | import { Subject } from 'rxjs/Subject'; 2 | import { map } from 'rxjs/operator/map'; 3 | import { switchMap } from 'rxjs/operator/switchMap'; 4 | import { ActionsObservable } from './ActionsObservable'; 5 | import { EPIC_END } from './EPIC_END'; 6 | 7 | const defaultAdapter = { 8 | input: action$ => action$, 9 | output: action$ => action$ 10 | }; 11 | 12 | const defaultOptions = { 13 | adapter: defaultAdapter 14 | }; 15 | 16 | export function createEpicMiddleware(epic, options = defaultOptions) { 17 | if (typeof epic !== 'function') { 18 | throw new TypeError('You must provide a root Epic to createEpicMiddleware'); 19 | } 20 | 21 | // even though we used default param, we need to merge the defaults 22 | // inside the options object as well in case they declare only some 23 | options = { ...defaultOptions, ...options }; 24 | const input$ = new Subject(); 25 | const action$ = options.adapter.input( 26 | new ActionsObservable(input$) 27 | ); 28 | const epic$ = new Subject(); 29 | let store; 30 | 31 | const epicMiddleware = _store => { 32 | store = _store; 33 | 34 | return next => { 35 | epic$ 36 | ::map(epic => { 37 | const output$ = ('dependencies' in options) 38 | ? epic(action$, store, options.dependencies) 39 | : epic(action$, store); 40 | 41 | if (!output$) { 42 | throw new TypeError(`Your root Epic "${epic.name || ''}" does not return a stream. Double check you\'re not missing a return statement!`); 43 | } 44 | 45 | return output$; 46 | }) 47 | ::switchMap(output$ => options.adapter.output(output$)) 48 | .subscribe(store.dispatch); 49 | 50 | // Setup initial root epic 51 | epic$.next(epic); 52 | 53 | return action => { 54 | debugger; 55 | const result = next(action); 56 | input$.next(action); 57 | return result; 58 | }; 59 | }; 60 | }; 61 | 62 | epicMiddleware.replaceEpic = epic => { 63 | // gives the previous root Epic a last chance 64 | // to do some clean up 65 | store.dispatch({ type: EPIC_END }); 66 | // switches to the new root Epic, synchronously terminating 67 | // the previous one 68 | epic$.next(epic); 69 | }; 70 | 71 | return epicMiddleware; 72 | } 73 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | export { createEpicMiddleware } from './createEpicMiddleware'; 2 | export { ActionsObservable } from './ActionsObservable'; 3 | export { combineEpics } from './combineEpics'; 4 | export { EPIC_END } from './EPIC_END'; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dive-into-rxjs", 3 | "version": "1.0.0", 4 | "description": "dive into rxjs", 5 | "main": "./src/index.js", 6 | "scripts": { 7 | "start": "opn http://localhost:8000 && node build.js ", 8 | "dev": "opn http://localhost:8080 && webpack-dev-server " 9 | }, 10 | "keywords": [ 11 | "rxjs", 12 | "reactive" 13 | ], 14 | "author": "slashhuang", 15 | "license": "ISC", 16 | "devDependencies": { 17 | "babel-core": "^6.6.5", 18 | "babel-loader": "^6.2.4", 19 | "babel-preset-es2015": "^6.6.0", 20 | "babel-preset-react": "^6.5.0", 21 | "babel-preset-stage-0": "^6.24.1", 22 | "express": "^4.15.2", 23 | "opn-cli": "^3.1.0", 24 | "react": "^0.14.7", 25 | "redux": "^3.3.1", 26 | "rimraf": "^2.6.1", 27 | "webpack": "^2.4.1", 28 | "webpack-dev-middleware": "^1.10.2", 29 | "webpack-dev-server": "^2.4.5", 30 | "webpack-hot-middleware": "^2.18.0" 31 | }, 32 | "dependencies": { 33 | "jquery": "^3.1.1", 34 | "lodash": "^4.6.1", 35 | "open": "^0.0.5", 36 | "react-redux": "^4.4.1", 37 | "redux": "^3.6.0", 38 | "redux-observable": "^0.14.1", 39 | "redux-thunk": "^2.0.1", 40 | "rxjs": "^5.3.1" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by slashhuang on 17/5/4. 3 | */ 4 | 5 | const path =require('path') 6 | const fs = require('fs') 7 | const webpack = require('webpack') 8 | const contextFilePath = path.resolve(__dirname,'example') 9 | module.exports = { 10 | devtool:"sourcemap", 11 | entry: fs.readdirSync(contextFilePath).reduce((pre,cur)=>{ 12 | let baseName = path.basename(cur,'.js') 13 | let fullPath = path.resolve(contextFilePath,cur) 14 | if(fs.statSync(fullPath).isDirectory()){ 15 | pre[baseName] = path.resolve(fullPath,'index.js') 16 | }else{ 17 | pre[baseName] = fullPath 18 | } 19 | return pre 20 | },{}), 21 | output: { 22 | publicPath:'/dist/', 23 | path: path.join(__dirname,'dist'), 24 | filename: "[name].js" 25 | }, 26 | resolve: { 27 | extensions: [ '.js', '.jsx'] 28 | }, 29 | plugins:[ 30 | new webpack.DefinePlugin({ 31 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development') 32 | }), 33 | new webpack.HotModuleReplacementPlugin(), 34 | new webpack.NoEmitOnErrorsPlugin() 35 | ], 36 | module: { 37 | rules: [ 38 | { test: /\.jsx?$/, 39 | loader: "babel-loader", 40 | exclude: /node_modules/ 41 | }, 42 | ] 43 | } 44 | }; 45 | --------------------------------------------------------------------------------