SlideShare a Scribd company logo
Compose Async with RxJS
@chitacan, Riiid
1
https://goo.gl/W1YChu
2 JSBin for example
RxJS ?
Reactive extensions library for JavaScript
3
시작하기 전에
[1, 2, 3, 4]
.map(d => d * 2)
.filter(d => d > 4)
.forEach(console.log);
4
시작하기 전에
[1, 2, 3, 4]
.map(d => d * 2)
.filter(d => d > 4)
.forEach(console.log);
4
> 6
> 8
Event 를 이렇게 처리한다면 어떨까요?
5
시작하기 전에
[..Event{}..Event{}...Event{}...]
.map(e => e.clientX * 2)
.filter(x => x > 4)
.forEach(updateUI);
6
시작하기 전에
[..Event{}..Event{}...Event{}...]
.map(e => e.clientX * 2)
.filter(x => x > 4)
.forEach(updateUI);
7
시작하기 전에
[..Event{}..Event{}...Event{}...]
.map(e => e.clientX * 2)
.filter(x => x > 4)
.forEach(updateUI);
8
시작하기 전에
[..Event{}..Event{}...Event{}...]
.map(e => e.clientX * 2)
.filter(x => x > 4)
.forEach(updateUI);
9
시작하기 전에
[..Event{}..Event{}...Event{}...]
.map(e => e.clientX * 2)
.filter(x => x > 4)
.forEach(updateUI);
[1, 2, 3, 4]
.map(d => d * 2)
.filter(d => d > 4)
.forEach(console.log);
10
Array 와 Event 는 모두 Collections
11
Event 는 데이터의 모음이고, Array 처럼 처리할 수 있다.
12
Array 와 Event
• 차이점
- array 는 각 요소를 동기적 (sync) 으로 조회할 수 있고, 끝이 있다.
- event 는 각 요소를 비동기적 (async) 으로 조회할 수 있고, 끝이 없다.

(하지만 event listening 을 취소 할 수 있다.)
• 비동기적으로 생성되는 요소들을 표현하고, 원하는 시점에 취소할
수 있는 타입
13
Observable
14
[ ]
time
collections over time
15
[ ]
time
collections over time
15
1....2..3..
pull vs push
[1, 2, 3].forEach(console.log);
[1..2..3...].forEach(console.log);
16
pull vs push
[1, 2, 3].forEach(console.log);
[1..2..3...].forEach(console.log);
16
> 1
> 2
> 3
pull vs push
[1, 2, 3].forEach(console.log);
[1..2..3...].forEach(console.log);
16
> 1
> 2
> 3
> 1
pull vs push
[1, 2, 3].forEach(console.log);
[1..2..3...].forEach(console.log);
16
> 1
> 2
> 3
> 1
> 2
pull vs push
[1, 2, 3].forEach(console.log);
[1..2..3...].forEach(console.log);
16
> 1
> 2
> 3
> 1
> 2
> 3
// ...
pull vs push
[1, 2, 3].forEach(console.log);
[1..2..3...].forEach(console.log);
16
> 1
> 2
> 3
> 1
> 2
> 3
// ...
pull
pull vs push
[1, 2, 3].forEach(console.log);
[1..2..3...].forEach(console.log);
16
> 1
> 2
> 3
> 1
> 2
> 3
// ...
pull
push
“RxJS 는 Observable 타입을 활용해 Event를
Array 처럼 처리 할 수 있는 라이브러리”
17
­Ben Lesh, RxJS In-Depth @AngularConnect 2015
“RxJS is LoDash or Underscore for async”
18
RxJS
• Observable 타입
• Operators (map, filter ...)
• Scheduler
19
Observable vs Promise
Promise Observable
single value multiple value
not lazy lazy
not cancelable cancelable
no completion callback completion callback
20
single value vs multiple value
• DOM / Event Emitter events (0 - N values)
• Animations (cancelable)
• REST API (1 value)
• WebSockets (0 - N values, retry)
• node.js core API (1 - N values)
21
single value vs multiple value
• DOM / Event Emitter events (0 - N values)
• Animations (cancelable)
• REST API (1 value)
• WebSockets (0 - N values)
• node.js core API (1 - N values)
22
Promise: not lazy
const p = new Promise(resolve => {
setTimeout(() => {
resolve(‘run');
}, 1000);
console.log('started');
});
23
Promise: not lazy
const p = new Promise(resolve => {
setTimeout(() => {
resolve(‘run');
}, 1000);
console.log('started');
});
24
executor
Promise: not lazy
const p = new Promise(resolve => {
setTimeout(() => {
resolve(‘run');
}, 1000);
console.log('started');
});
25 https://goo.gl/tK39aS
Promise: not lazy
const p = new Promise(resolve => {
setTimeout(() => {
resolve(‘run');
}, 1000);
console.log('started');
});
25
> "started"
https://goo.gl/tK39aS
const source = Rx.Observable.create(observer => {
setTimeout(() => {
observer.next('run');
}, 1000);
console.log('started');
});
Observable: lazy
26
const source = Rx.Observable.create(observer => {
setTimeout(() => {
observer.next('run');
}, 1000);
console.log('started');
});
Observable: lazy
26
// no console output
const source = Rx.Observable.create(observer => {
setTimeout(() => {
observer.next('run');
}, 1000);
console.log('started');
});
source.subscribe(val => console.log(val));
Observable: lazy
27 https://goo.gl/qh24FK
const source = Rx.Observable.create(observer => {
setTimeout(() => {
observer.next('run');
}, 1000);
console.log('started');
});
source.subscribe(val => console.log(val));
Observable: lazy
27
> "started"
https://goo.gl/qh24FK
const source = Rx.Observable.create(observer => {
setTimeout(() => {
observer.next('run');
}, 1000);
console.log('started');
});
source.subscribe(val => console.log(val));
Observable: lazy
27
> "started"
> "run"
https://goo.gl/qh24FK
Promise: not cancelable
const p = new Promise(resolve => {
var val = 0;
setInterval(() => {
console.log(`val: ${val}`)
resolve(++val);
}, 1000);
console.log('started');
});
p.then(val => console.log(`result: ${val}`));
28
Promise: not cancelable
const p = new Promise(resolve => {
var val = 0;
setInterval(() => {
console.log(`val: ${val}`)
resolve(++val);
}, 1000);
console.log('started');
});
p.then(val => console.log(`result: ${val}`));
28
> "started"
Promise: not cancelable
const p = new Promise(resolve => {
var val = 0;
setInterval(() => {
console.log(`val: ${val}`)
resolve(++val);
}, 1000);
console.log('started');
});
p.then(val => console.log(`result: ${val}`));
28
> "started"
> "val: 0"
Promise: not cancelable
const p = new Promise(resolve => {
var val = 0;
setInterval(() => {
console.log(`val: ${val}`)
resolve(++val);
}, 1000);
console.log('started');
});
p.then(val => console.log(`result: ${val}`));
28
> "started"
> "val: 0"
> "result: 0” // promise result
> "val: 1"
> "val: 2"
> "val: 3" // ??????????
Observable: cancelable
const source = Rx.Observable.create(observer => {
var val = 0;
const id = setInterval(() => {
console.log(`val: ${val}`)
observer.next(++val);
}, 1000);
console.log('started');
return () => {
clearInterval(id);
console.log('cancelled');
};
});
const subscription = source.subscribe(val => console.log(`result: ${val}`));
setTimeout(() => subscription.unsubscribe(), 5000);
29 https://goo.gl/qJ1zRi
Observable: cancelable
const source = Rx.Observable.create(observer => {
var val = 0;
const id = setInterval(() => {
console.log(`val: ${val}`)
observer.next(++val);
}, 1000);
console.log('started');
return () => {
clearInterval(id);
console.log('cancelled');
};
});
const subscription = source.subscribe(val => console.log(`result: ${val}`));
setTimeout(() => subscription.unsubscribe(), 5000);
> "started"
> "val: 0"
> "result: 0"
> "val: 1"
> "result: 1"
// ...
30 https://goo.gl/qJ1zRi
Observable: cancelable
const source = Rx.Observable.create(observer => {
var val = 0;
const id = setInterval(() => {
console.log(`val: ${val}`)
observer.next(++val);
}, 1000);
console.log('started');
return () => {
clearInterval(id);
console.log('cancelled');
};
});
const subscription = source.subscribe(val => console.log(`result: ${val}`));
setTimeout(() => subscription.unsubscribe(), 5000);
> "started"
> "val: 0"
> "result: 0"
> "val: 1"
> "result: 1"
// ...
30
teardown logic
https://goo.gl/qJ1zRi
Observable: cancelable
const source = Rx.Observable.create(observer => {
var val = 0;
const id = setInterval(() => {
console.log(`val: ${val}`)
observer.next(++val);
}, 1000);
console.log('started');
return () => {
clearInterval(id);
console.log('cancelled');
};
});
const subscription = source.subscribe(val => console.log(`result: ${val}`));
setTimeout(() => subscription.unsubscribe(), 5000);
> "started"
> "val: 0"
> "result: 0"
> "val: 1"
> "result: 1"
// ...
30
> "cancelled"
teardown logic
https://goo.gl/qJ1zRi
promise.then(valueFn, errorFn);
Completion
31
Completion
32
observable.subscribe(nextFn, errorFn, completeFn);
Operators
• observable 타입을 변환 / 조합할 수 있음
• RxJS@5 기준 +200개
• 종류
- creation
- transformation
- filtering
- composition
- error
- ...
33
creation
• create
• from
• fromEvent
• fromPromise
• interval
34
from
const source = Rx.Observable.from([1,2,3,4,5])
source.subscribe(val => console.log(val));
> 1
> 2
> 3
> 4
> 5
35
Array function vs RxJS
const source = [1,2,3,4,5];
const result = source
.filter(d => d % 2 === 0)
.map(d => d + '!')
.reduce((p, d) => p + d)
console.log(result);
> "2!4!"
36
Array function vs RxJS
const source =[1,2,3,4,5];
const result = source
.filter((d, i, arr) => {
console.log(`filter: ${d}`);
return d % 2 === 0;
})
.map((d, i, arr) => {
console.log(`map: ${d}`);
return d + '!';
})
.reduce((p, d, i, arr) => {
console.log(`reduce: ${d}`);
return p + d;
})
console.log(result);
> "filter: 1"
> "filter: 2"
> "filter: 3"
> "filter: 4"
> "filter: 5"
> "map: 2"
> "map: 4"
> "reduce: 2!"
> "reduce: 4!"
> "2!4!"
37 https://goo.gl/ma2vxb
Array function vs RxJS
const source = Rx.Observable.from[1,2,3,4,5];
source
.filter(d => d % 2 === 0)
.map(d => d + '!')
.reduce((p, d) => p + d)
.subscribe(result => console.log(result));
> "2!4!"
38
Array function vs RxJS
const source = Rx.Observable.from([1,2,3,4,5]);
source
.filter(d => {
console.log(`filter: ${d}`);
return d % 2 === 0;
})
.map(d => {
console.log(`map: ${d}`);
return d + '!';
})
.reduce((p, d) => {
console.log(`reduce: ${d}`);
return p + d;
}, '')
.subscribe(result => console.log(result));
> "filter: 1"
> "filter: 2"
> "map: 2"
> "reduce: 2!"
> "filter: 3"
> "filter: 4"
> "map: 4"
> "reduce: 4!"
> "filter: 5"
> "2!4!"
39 https://goo.gl/5dtLXF
Array function vs RxJS
const source = Rx.Observable.from([1,2,3,4,5]);
source
.filter(d => {
console.log(`filter: ${d}`);
return d % 2 === 0;
})
.map(d => {
console.log(`map: ${d}`);
return d + '!';
})
.reduce((p, d) => {
console.log(`reduce: ${d}`);
return p + d;
}, '')
.subscribe(result => console.log(result));
> "filter: 1"
> "filter: 2"
> "map: 2"
> "reduce: 2!"
> "filter: 3"
> "filter: 4"
> "map: 4"
> "reduce: 4!"
> "filter: 5"
> "2!4!"
40 https://goo.gl/5dtLXF
Array function
41
Array function
41
RxJS
42
RxJS
42
fromEvent
const source = Rx.Observable.fromEvent(document, ‘click');
source.subscribe(val => console.log(val));
43
fromEvent
const source = Rx.Observable.fromEvent(document, 'click');
.map(event => event.x)
.filter(x => x > 100);
source.subscribe(val => console.log(val));
44
fromPromise
const request = axios.get(URL);
const source = Rx.Observable.fromPromise(request);
.map(res => res.data);
source.subscribe(val => console.log(val));
45
interval
const source = Rx.Observable.interval(1000);
source.subscribe(val => {
console.log(val);
}, err => {
console.error(err);
}, () => {
console.log('completed');
});
46 https://goo.gl/ybHNDZ
interval
const source = Rx.Observable.interval(1000);
source.subscribe(val => {
console.log(val);
}, err => {
console.error(err);
}, () => {
console.log('completed');
});
46
> 0
> 1
> 2
> 3
// ...
https://goo.gl/ybHNDZ
interval
const source = Rx.Observable.interval(1000);
source.subscribe(val => {
console.log(val);
}, err => {
console.error(err);
}, () => {
console.log('completed');
});
46
> 0
> 1
> 2
> 3
// ...
// "completed" will not be printed
https://goo.gl/ybHNDZ
transformation
• map
• mergeMap (flatMap) 🌟
47
map
const source = Rx.Observable.from([1,2,3,4])
.map(d => d * 10);
source.subscribe(val => console.log(val));
> 10
> 20
> 30
> 40
48
mergeMap (flatMap)
_.flatMap([1, 2], d => [d, d]);
// [[1, 1], [2, 2]]
> [1, 1, 2, 2]
_.flatten([[1, 1], [2, 2]]);
> [1, 1, 2, 2]
49
mergeMap (flatMap)
[..1..2..3].mergeMap(d => [...d])
[[...1]..[...2]..[...3]]
[....1......2......3]
50
source projection
result
mergeMap (flatMap)
[..1..2..3].mergeMap(d => [...d])
[[...1]..[...2]..[...3]]
[....1......2......3]
50
source projection
result
mergeMap (flatMap)
[..1..2..3].mergeMap(d => [...d])
[[...1]..[...2]..[...3]]
[....1......2......3]
50
source projection
result
mergeMap (flatMap)
[..1..2..3].mergeMap(d => [...d])
[[...1]..[...2]..[...3]]
[....1......2......3]
50
source projection
result
mergeMap (flatMap)
[..1..2..3].mergeMap(d => [...d])
[[...1]..[...2]..[...3]]
[....1......2......3]
50
source projection
result
mergeMap (flatMap)
[..1..2..3].mergeMap(d => [...d])
[[...1]..[...2]..[...3]]
[....1......2......3]
50
source projection
result
mergeMap (flatMap)
[..1..2..3].mergeMap(d => [...d])
[[...1]..[...2]..[...3]]
[....1......2......3]
50
source projection
result
mergeMap (flatMap)
[..1..2..3].mergeMap(d => [...d])
[[...1]..[...2]..[...3]]
[....1......2......3]
50
source projection
result
mergeMap (flatMap)
[..1..2..3].mergeMap(d => [...d])
[[...1]..[...2]..[...3]]
[....1......2......3]
50
source projection
result
mergeMap (flatMap)
[..1..2..3].mergeMap(d => [...d])
[[...1]..[...2]..[...3]]
[....1......2......3]
50
source projection
result
mergeMap (flatMap)
const source = requestUser$()
.mergeMap(res => {
const {data: {userId}} = res;
return requestPosts$({userId});
});
source.subscribe(res => console.log(res));
51 https://goo.gl/SHgVZ4
mergeMap (flatMap)
const source = requestUser$()
.mergeMap(res => {
const {data: {userId}} = res;
return requestPosts$({userId});
});
source.subscribe(res => console.log(res));
52 https://goo.gl/SHgVZ4
mergeMap (flatMap)
const source = requestUser$() // [.....user]
.mergeMap(res => {
const {data: {userId}} = res;
return requestPosts$({userId}); // [....[..posts]]
});
source.subscribe(res => console.log(res)); // [.....posts]
53 https://goo.gl/SHgVZ4
mergeMap (flatMap)
const source = requestUser$()
.mergeMap(res => {
const {data: {userId}} = res;
return axios.get(`${URL}/posts`, {params: {id: userId}});
});
source.subscribe(res => console.log(res.data));
54 https://goo.gl/FKaQLr
filtering
• filter
• take
• takeUntil 🌟
55
filter
const source = Rx.Observable.from([1,2,3,4])
.filter(d => d > 3);
source.subscribe(val => console.log(val));
> 4
56
take
const source = Rx.Observable.interval(1000)
.take(3);
source.subscribe(val => {
console.log(val);
}, err => {
console.error(err);
}, () => {
console.log('completed');
});
57 https://goo.gl/NkGcDo
take
const source = Rx.Observable.interval(1000)
.take(3);
source.subscribe(val => {
console.log(val);
}, err => {
console.error(err);
}, () => {
console.log('completed');
});
57
> 0
> 1
> 2
> "completed"
https://goo.gl/NkGcDo
takeUntil
[...0...1...2...3].takeUntil(
[.............0])
[...0...1...2.]
58
source
notifier
result
takeUntil
[...0...1...2...3].takeUntil(
[.............0])
[...0...1...2.]
58
source
notifier
result
takeUntil
[...0...1...2...3].takeUntil(
[.............0])
[...0...1...2.]
58
source
notifier
result
takeUntil
[...0...1...2...3].takeUntil(
[.............0])
[...0...1...2.]
58
source
notifier
result
takeUntil
[...0...1...2...3].takeUntil(
[.............0])
[...0...1...2.]
58
source
notifier
result
takeUntil
const source = Rx.Observable.interval(1000)
.takeUntil(Rx.Observable.timer(3500));
source.subscribe(val => {
console.log(val);
}, err => {
console.error(err);
}, () => {
console.log('completed');
});
59 https://goo.gl/ZaXJFS
takeUntil
const source = Rx.Observable.interval(1000)
.takeUntil(Rx.Observable.timer(3500));
source.subscribe(val => {
console.log(val);
}, err => {
console.error(err);
}, () => {
console.log('completed');
});
60 https://goo.gl/ZaXJFS
takeUntil
const source = Rx.Observable.interval(1000)
.takeUntil(Rx.Observable.timer(3500));
source.subscribe(val => {
console.log(val);
}, err => {
console.error(err);
}, () => {
console.log('completed');
});
61 https://goo.gl/ZaXJFS
takeUntil
const source = Rx.Observable.interval(1000)
.takeUntil(Rx.Observable.timer(3500));
source.subscribe(val => {
console.log(val);
}, err => {
console.error(err);
}, () => {
console.log('completed');
});
61
> 0
> 1
> 2
> "completed"
https://goo.gl/ZaXJFS
composition
• combineLatest 🌟
• zip
• merge
62
combineLatest
combineLatest(
[...0...1...2...3...4...], // source1
[.......@], // source2
[...........#] // source3
)
[
...........[2, @, #]...[3, @, #]...[4, @, #]...
]
63
combineLatest
combineLatest(
[...0...1...2...3...4...], // source1
[.......@], // source2
[...........#] // source3
)
[
...........[2, @, #]...[3, @, #]...[4, @, #]...
]
63
combineLatest
combineLatest(
[...0...1...2...3...4...], // source1
[.......@], // source2
[...........#] // source3
)
[
...........[2, @, #]...[3, @, #]...[4, @, #]...
]
63
combineLatest
combineLatest(
[...0...1...2...3...4...], // source1
[.......@], // source2
[...........#] // source3
)
[
...........[2, @, #]...[3, @, #]...[4, @, #]...
]
63
@
combineLatest
combineLatest(
[...0...1...2...3...4...], // source1
[.......@], // source2
[...........#] // source3
)
[
...........[2, @, #]...[3, @, #]...[4, @, #]...
]
63
@ @
#
combineLatest
combineLatest(
[...0...1...2...3...4...], // source1
[.......@], // source2
[...........#] // source3
)
[
...........[2, @, #]...[3, @, #]...[4, @, #]...
]
63
@ @ @
# #
combineLatest
const source1 = Rx.Observable.interval(1000);
const source2 = Rx.Observable.timer(2000).mapTo(‘@');
const source3 = Rx.Observable.timer(3000).mapTo(‘#’);
Rx.Observable.combineLatest(source1, source2, source3)
.subscribe(result => console.log(result));
64 https://goo.gl/t9vpLF
combineLatest
const source1 = Rx.Observable.interval(1000);
const source2 = Rx.Observable.timer(2000).mapTo(‘@');
const source3 = Rx.Observable.timer(3000).mapTo(‘#’);
Rx.Observable.combineLatest(source1, source2, source3)
.subscribe(result => console.log(result));
64
> [2, “@", “#"]
> [3, “@", “#"]
> [4, “@", “#"]
// ...
https://goo.gl/t9vpLF
zip
zip(
[...0...1...2...3...4...5], // source1
[.....0.....1.....2...] // source2
)
[
....[0, 0]....[1, 1]....[2, 2]...
]
65
zip
zip(
[...0...1...2...3...4...5], // source1
[.....0.....1.....2...] // source2
)
[
....[0, 0]....[1, 1]....[2, 2]...
]
65
zip
zip(
[...0...1...2...3...4...5], // source1
[.....0.....1.....2...] // source2
)
[
....[0, 0]....[1, 1]....[2, 2]...
]
65
zip
zip(
[...0...1...2...3...4...5], // source1
[.....0.....1.....2...] // source2
)
[
....[0, 0]....[1, 1]....[2, 2]...
]
65
zip
const source1 = Rx.Observable.interval(1000);
const source2 = Rx.Observable.interval(2000);
Rx.Observable.zip(source1, source2)
.subscribe(result => console.log(result));
66 https://goo.gl/wmQW7m
zip
const source1 = Rx.Observable.interval(1000);
const source2 = Rx.Observable.interval(2000);
Rx.Observable.zip(source1, source2)
.subscribe(result => console.log(result));
66
> [0, 0]
> [1, 1]
> [2, 2]
// ...
https://goo.gl/wmQW7m
merge
const source1 = Rx.Observable.interval(1000);
const source2 = Rx.Observable.timer(2500).mapTo('source2');
Rx.Observable.merge(source1, source2)
.subscribe(result => console.log(result));
> 0
> 1
> 2
> "source2"
> 3
// ...
67 https://goo.gl/OqaQf9
error
• retry 🌟
• retryWhen 🌟
68
retry
Rx.Observable.interval(1000)
.map(val => {
if (val > 2) {
throw new Error('over 2');
}
return val;
})
.retry(2)
.subscribe(result => {
console.log(result);
}, err => {
console.error(`error: ${error.message}`);
});
69 https://goo.gl/5zFqOl
retry
Rx.Observable.interval(1000)
.map(val => {
if (val > 2) {
throw new Error('over 2');
}
return val;
})
.retry(2)
.subscribe(result => {
console.log(result);
}, err => {
console.error(`error: ${error.message}`);
});
70 https://goo.gl/5zFqOl
retry
Rx.Observable.interval(1000)
.map(val => {
if (val > 2) {
throw new Error('over 2');
}
return val;
})
.retry(2)
.subscribe(result => {
console.log(result);
}, err => {
console.error(`error: $error.message}`);
});
71 https://goo.gl/5zFqOl
retry
Rx.Observable.interval(1000)
.map(val => {
if (val > 2) {
throw new Error('over 2');
}
return val;
})
.retry(2)
.subscribe(result => {
console.log(result);
}, err => {
console.error(`error: $error.message}`);
});
71 https://goo.gl/5zFqOl
> 0
> 1
> 2
> “error: over 2"
> 0
> 1
> 2
> 0
> 1
> 2
retryWhen
[...0...1...2...3].retryWhen(err => [......0])
[...0...1...2........0...1...2........0...1...2.]
72
source notifier
result
retryWhen
[...0...1...2...3].retryWhen(err => [......0])
[...0...1...2........0...1...2........0...1...2.]
72
source notifier
result
retryWhen
[...0...1...2...3].retryWhen(err => [......0])
[...0...1...2........0...1...2........0...1...2.]
72
source notifier
result
retryWhen
[...0...1...2...3].retryWhen(err => [......0])
[...0...1...2........0...1...2........0...1...2.]
72
source notifier
result
retryWhen
[...0...1...2...3].retryWhen(err => [......0])
[...0...1...2........0...1...2........0...1...2.]
72
source notifier
result
💥
retryWhen
[...0...1...2...3].retryWhen(err => [......0])
[...0...1...2........0...1...2........0...1...2.]
72
source notifier
result
💥
retryWhen
[...0...1...2...3].retryWhen(err => [......0])
[...0...1...2........0...1...2........0...1...2.]
72
source notifier
result
retryWhen
[...0...1...2...3].retryWhen(err => [......0])
[...0...1...2........0...1...2........0...1...2.]
72
source notifier
result
retryWhen
[...0...1...2...3].retryWhen(err => [......0])
[...0...1...2........0...1...2........0...1...2.]
72
source notifier
result
retryWhen
[...0...1...2...3].retryWhen(err => [......0])
[...0...1...2........0...1...2........0...1...2.]
72
source notifier
result
💥
retryWhen
[...0...1...2...3].retryWhen(err => [......0])
[...0...1...2........0...1...2........0...1...2.]
72
source notifier
result
💥
retryWhen
[...0...1...2...3].retryWhen(err => [......0])
[...0...1...2........0...1...2........0...1...2.]
72
source notifier
result
retryWhen
[...0...1...2...3].retryWhen(err => [......0])
[...0...1...2........0...1...2........0...1...2.]
72
source notifier
result
retryWhen
[...0...1...2...3].retryWhen(err => [......0])
[...0...1...2........0...1...2........0...1...2.]
72
source notifier
result
retryWhen
73
Rx.Observable.interval(1000)
.map(val => {
if (val > 2) {
throw new Error('over 2');
}
return val;
})
.retryWhen(error => {
return error
.do(err => console.error(err.message))
.delay(2000);
})
.subscribe(result => console.log(result));
https://goo.gl/9bhNHh
retryWhen
74
Rx.Observable.interval(1000)
.map(val => {
if (val > 2) {
throw new Error('over 2');
}
return val;
})
.retryWhen(error => {
return error
.do(err => console.error(err.message))
.delay(2000);
})
.subscribe(result => console.log(result));
https://goo.gl/9bhNHh
retryWhen
75
Rx.Observable.interval(1000)
.map(val => {
if (val > 2) {
throw new Error('over 2');
}
return val;
})
.retryWhen(error => {
return error
.do(err => console.error(err.message))
.delay(2000);
})
.subscribe(result => console.log(result));
https://goo.gl/9bhNHh
retryWhen
75
Rx.Observable.interval(1000)
.map(val => {
if (val > 2) {
throw new Error('over 2');
}
return val;
})
.retryWhen(error => {
return error
.do(err => console.error(err.message))
.delay(2000);
})
.subscribe(result => console.log(result));
https://goo.gl/9bhNHh
> "over 2"
> 0
> 1
> 2
> "over 2"
> 0
> 1
> 2
> "over 2"
> 0
> 1
> 2
// ...
operators
• switchMap 🌟
• concatMap 🌟
• publish
• share
• skip
• bufferCount
• bufferTime
• Throttle
• debounce
76
• concat
• catch
• bindNodeCallback
• +others
Compose async with RxJS
• 여러 이벤트를 조합해야 할 때 (Drag & Drop)
• 다수의 비동기 함수를 조합하는 상황 (Cache or Db, AWS Lambda)
• 재시도가 필요한 경우 (on / offline)
77
Drag & Drop
• mousedown, mousemove, mouseup 3가지 유저 이벤트를 적절
하게 조합해야 함
- mousedown 이벤트가 발생하면 현재 엘리먼트의 초기 위치를 저장
- mousemove 이벤트가 발생하면 초기 위치 + 변한 위치를 계산해 새로운
위치로 엘리먼트를 옮김
- 언제까지? mouseup 이벤트가 발생할 때까지
78
Drag & Drop
const target = document.getElementById('target');
const down = Rx.Observable.fromEvent(target, 'mousedown');
const move = Rx.Observable.fromEvent(document, 'mousemove');
const up = Rx.Observable.fromEvent(target, 'mouseup');
const drag = down.flatMap(downEvent => {
const downX = downEvent.clientX;
const downY = downEvent.clientY;
const downLeft = parseInt(downEvent.target.style.left, 10) || 0;
const downTop = parseInt(downEvent.target.style.top, 10) || 0;
return move.map(moveEvent => {
moveEvent.preventDefault();
return {
left: moveEvent.clientX + (downLeft - downX),
top : moveEvent.clientY + (downTop - downY)
};
})
.takeUntil(up);
});
drag.subscribe(result => {
target.style.top = result.top + 'px';
target.style.left = result.left + 'px';
});
79 https://goo.gl/nyeK8l
Drag & Drop
const target = document.getElementById('target');
const down = Rx.Observable.fromEvent(target, 'mousedown');
const move = Rx.Observable.fromEvent(document, 'mousemove');
const up = Rx.Observable.fromEvent(target, 'mouseup');
const drag = down.flatMap(downEvent => {
const downX = downEvent.clientX;
const downY = downEvent.clientY;
const downLeft = parseInt(downEvent.target.style.left, 10) || 0;
const downTop = parseInt(downEvent.target.style.top, 10) || 0;
return move.map(moveEvent => {
moveEvent.preventDefault();
return {
left: moveEvent.clientX + (downLeft - downX),
top : moveEvent.clientY + (downTop - downY)
};
})
.takeUntil(up);
});
drag.subscribe(result => {
target.style.top = result.top + 'px';
target.style.left = result.left + 'px';
});
80 https://goo.gl/nyeK8l
Drag & Drop
const target = document.getElementById('target');
const down = Rx.Observable.fromEvent(target, 'mousedown');
const move = Rx.Observable.fromEvent(document, 'mousemove');
const up = Rx.Observable.fromEvent(target, 'mouseup');
const drag = down.flatMap(downEvent => {
const downX = downEvent.clientX;
const downY = downEvent.clientY;
const downLeft = parseInt(downEvent.target.style.left, 10) || 0;
const downTop = parseInt(downEvent.target.style.top, 10) || 0;
return move.map(moveEvent => {
moveEvent.preventDefault();
return {
left: moveEvent.clientX + (downLeft - downX),
top : moveEvent.clientY + (downTop - downY)
};
})
.takeUntil(up);
});
drag.subscribe(result => {
target.style.top = result.top + 'px';
target.style.left = result.left + 'px';
});
81 https://goo.gl/nyeK8l
Drag & Drop
const target = document.getElementById('target');
const down = Rx.Observable.fromEvent(target, 'mousedown');
const move = Rx.Observable.fromEvent(document, 'mousemove');
const up = Rx.Observable.fromEvent(target, 'mouseup');
const drag = down.flatMap(downEvent => {
const downX = downEvent.clientX;
const downY = downEvent.clientY;
const downLeft = parseInt(downEvent.target.style.left, 10) || 0;
const downTop = parseInt(downEvent.target.style.top, 10) || 0;
return move.map(moveEvent => {
moveEvent.preventDefault();
return {
left: moveEvent.clientX + (downLeft - downX),
top : moveEvent.clientY + (downTop - downY)
};
})
.takeUntil(up);
});
drag.subscribe(result => {
target.style.top = result.top + 'px';
target.style.left = result.left + 'px';
});
82 https://goo.gl/nyeK8l
Drag & Drop
const target = document.getElementById('target');
const down = Rx.Observable.fromEvent(target, 'mousedown');
const move = Rx.Observable.fromEvent(document, 'mousemove');
const up = Rx.Observable.fromEvent(target, 'mouseup');
const drag = down.flatMap(downEvent => {
const downX = downEvent.clientX;
const downY = downEvent.clientY;
const downLeft = parseInt(downEvent.target.style.left, 10) || 0;
const downTop = parseInt(downEvent.target.style.top, 10) || 0;
return move.map(moveEvent => {
moveEvent.preventDefault();
return {
left: moveEvent.clientX + (downLeft - downX),
top : moveEvent.clientY + (downTop - downY)
};
})
.takeUntil(up);
});
drag.subscribe(result => {
target.style.top = result.top + 'px';
target.style.left = result.left + 'px';
});
83 https://goo.gl/nyeK8l
Drag & Drop
const target = document.getElementById('target');
const down = Rx.Observable.fromEvent(target, 'mousedown');
const move = Rx.Observable.fromEvent(document, 'mousemove');
const up = Rx.Observable.fromEvent(target, 'mouseup');
const drag = down.flatMap(downEvent => {
const downX = downEvent.clientX;
const downY = downEvent.clientY;
const downLeft = parseInt(downEvent.target.style.left, 10) || 0;
const downTop = parseInt(downEvent.target.style.top, 10) || 0;
return move.map(moveEvent => {
moveEvent.preventDefault();
return {
left: moveEvent.clientX + (downLeft - downX),
top : moveEvent.clientY + (downTop - downY)
};
})
.takeUntil(up);
});
drag.subscribe(result => {
target.style.top = result.top + 'px';
target.style.left = result.left + 'px';
});
84 https://goo.gl/nyeK8l
Cache or DB
85
• cache 또는 DB 에서 데이터를 가져오는 상황
- getFromCache$(), getFromDB$()
- 둘 중에 먼저 도착한 데이터만 사용하고 싶다.
- 나머지 요청은 취소되어야 함
- getFromOtherServer$()
- 도착한 데이터의 값을 사용해 다른 요청 수행
Cache or Db
Rx.Observable.merge(getFromCache$(), getFromDB$())
.take(1)
.flatMap(data => getFromOtherServer$(data.id))
.subscribe(result => {
res.json(result)
}, err => {
res.status(500).send(err.message)
});
86
Cache or Db
Rx.Observable.merge(getFromCache$(), getFromDB$())
.take(1)
.flatMap(data => getFromOtherServer$(data.id))
.subscribe(result => {
res.json(result)
}, err => {
res.status(500).send(err.message)
});
87
Cache or Db
Rx.Observable.merge(getFromCache$(), getFromDB$())
.take(1)
.flatMap(data => getFromOtherServer$(data.id))
.subscribe(result => {
res.json(result)
}, err => {
res.status(500).send(err.message)
});
88
AWS Lambda
89
• 매일 아침에 태스크 큐를 확인해서 실패한 태스크 워커가 있으면,
슬랙을 통해 알림을 받고 싶다.
AWS Lambda
90
• Task function
- getTasks$()
- 태스크 큐에 쌓여있는 태스크 정보 가져오기
- checkNotified$()
- renderTaskCount$()
- 실패한 태스크가 존재하면 vega-renderer function 호출
• vega-renderer function
- upload$()
- 렌더링된 이미지를 s3 에 저장
- post$()
- 저장된 이미지 경로와 함께 슬랙에 알림 전송
Task function
export default (e, ctx, cb) => {
const tasks$ = getTasks$();
const notified$ = checkNotified$();
combineLatest(tasks$, notified$, (tasks, notified) => {
if (notified) {
return O.return('already notified');
} else if (tasks.length === 0) {
return O.return('queue empty');
}
return renderTaskCount$(tasks);
})
.concatAll()
.subscribe(result => {
ctx.callbackWaitsForEmptyEventLoop = false;
cb(null, result);
}, err => {
ctx.callbackWaitsForEmptyEventLoop = false;
cb(err);
});
};
91
Task function
export default (e, ctx, cb) => {
const tasks$ = getTasks$();
const notified$ = checkNotified$();
combineLatest(tasks$, notified$, (tasks, notified) => {
if (notified) {
return O.return('already notified');
} else if (tasks.length === 0) {
return O.return('queue empty');
}
return renderTaskCount$(tasks);
})
.concatAll()
.subscribe(result => {
ctx.callbackWaitsForEmptyEventLoop = false;
cb(null, result);
}, err => {
ctx.callbackWaitsForEmptyEventLoop = false;
cb(err);
});
};
92
Task function
export default (e, ctx, cb) => {
const tasks$ = getTasks$();
const notified$ = checkNotified$();
combineLatest(tasks$, notified$, (tasks, notified) => {
if (notified) {
return O.return('already notified');
} else if (tasks.length === 0) {
return O.return('queue empty');
}
return renderTaskCount$(tasks);
})
.concatAll()
.subscribe(result => {
ctx.callbackWaitsForEmptyEventLoop = false;
cb(null, result);
}, err => {
ctx.callbackWaitsForEmptyEventLoop = false;
cb(err);
});
};
93
Task function
export default (e, ctx, cb) => {
const tasks$ = getTasks$();
const notified$ = checkNotified$();
combineLatest(tasks$, notified$, (tasks, notified) => {
if (notified) {
return O.return('already notified');
} else if (tasks.count === 0) {
return O.return('queue empty');
}
return renderTaskCount$(tasks);
})
.concatAll()
.subscribe(result => {
ctx.callbackWaitsForEmptyEventLoop = false;
cb(null, result);
}, err => {
ctx.callbackWaitsForEmptyEventLoop = false;
cb(err);
});
};
94
Task function
95
const lambda = new Aws.Lambda();
const invoke$ = (FunctionName, Payload, InvocationType = 'RequestResponse') => {
return Rx.Observable.bindNodeCallback(lambda.invoke, lambda)({
FunctionName, Payload, InvocationType
});
};
export const renderTaskCount$ = values => {
return invoke$('vega-renderer_png', JSON.stringify({
isLite: true,
spec: Object.assign(TASK_SPEC, {data: {values}})
}))
.map(res => JSON.parse(res.Payload));
};
Task function
96
const lambda = new Aws.Lambda();
const invoke$ = (FunctionName, Payload, InvocationType = 'RequestResponse') => {
return Rx.Observable.bindNodeCallback(lambda.invoke, lambda)({
FunctionName, Payload, InvocationType
});
};
export const renderTaskCount$ = values => {
return invoke$('vega-renderer_png', JSON.stringify({
isLite: true,
spec: Object.assign(TASK_SPEC, {data: {values}})
}))
.map(res => JSON.parse(res.Payload));
};
Task function
97
const lambda = new Aws.Lambda();
const invoke$ = (FunctionName, Payload, InvocationType = 'RequestResponse') => {
return Rx.Observable.bindNodeCallback(lambda.invoke, lambda)({
FunctionName, Payload, InvocationType
});
};
export const renderTaskCount$ = values => {
return invoke$('vega-renderer_png', JSON.stringify({
isLite: true,
spec: Object.assign(TASK_SPEC, {data: {values}})
}))
.map(res => JSON.parse(res.Payload));
};
vega-renderer function
98
const s3 = new Aws.S3();
const upload$ = Rx.Observable.bindNodeCallback(s3.upload, s3);
export default (event, ctx, cb) => {
const {spec, isLite} = event;
const filename = hash(spec);
spec$(spec, isLite)
.map(chart => png(chart, filename))
.flatMap(image => upload$(image))
.flatMap(data => post$(event, data.Location))
.subscribe(result => {
cb(null, result);
}, err => {
console.log(err);
cb(err);
});
};
vega-renderer function
99
const s3 = new Aws.S3();
const upload$ = Rx.Observable.bindNodeCallback(s3.upload, s3);
export default (event, ctx, cb) => {
const {spec, isLite} = event;
const filename = hash(spec);
spec$(spec, isLite)
.map(chart => png(chart, filename))
.flatMap(image => upload$(image))
.flatMap(data => post$(event, data.Location))
.subscribe(result => {
cb(null, result);
}, err => {
console.log(err);
cb(err);
});
};
vega-renderer function
100
const s3 = new Aws.S3();
const upload$ = Rx.Observable.bindNodeCallback(s3.upload, s3);
export default (event, ctx, cb) => {
const {spec, isLite} = event;
const filename = hash(spec);
spec$(spec, isLite)
.map(chart => png(chart, filename))
.flatMap(image => upload$(image))
.flatMap(data => post$(event, data.Location))
.subscribe(result => {
cb(null, result);
}, err => {
console.log(err);
cb(err);
});
};
vega-renderer function
101
const s3 = new Aws.S3();
const upload$ = Rx.Observable.bindNodeCallback(s3.upload, s3);
export default (event, ctx, cb) => {
const {spec, isLite} = event;
const filename = hash(spec);
spec$(spec, isLite)
.map(chart => png(chart, filename))
.flatMap(image => upload$(image))
.flatMap(data => post$(event, data.Location))
.subscribe(result => {
cb(null, result);
}, err => {
console.log(err);
cb(err);
});
};
vega-renderer function
102
const s3 = new Aws.S3();
const upload$ = Rx.Observable.bindNodeCallback(s3.upload, s3);
export default (event, ctx, cb) => {
const {spec, isLite} = event;
const filename = hash(spec);
spec$(spec, isLite)
.map(chart => png(chart, filename))
.flatMap(image => upload$(image))
.flatMap(data => post$(event, data.Location))
.subscribe(result => {
cb(null, result);
}, err => {
console.log(err);
cb(err);
});
};
103
on / offline
104
• 에러가 발생했을때
- online 상태인 경우, 1초 뒤에 재시도
- offline 상태인 경우, online 상태가 되면 재시도
on / offline
Rx.Observable.interval(1000)
.map(val => {
if (val > 5) {
throw new Error('too high');
}
return val;
})
.retryWhen(errors => {
return errors.delayWhen(() => {
const online = window.navigator.onLine;
return online ?
Rx.Observable.timer(1000) :
Rx.Observable.fromEvent(window, 'online')
});
})
.subscribe(val => console.log(val))
105 https://goo.gl/SAmmsD
on / offline
106 https://goo.gl/SAmmsD
Rx.Observable.interval(1000)
.map(val => {
if (val > 5) {
throw new Error('too high');
}
return val;
})
.retryWhen(errors => {
return errors.delayWhen(() => {
const online = window.navigator.onLine;
return online ?
Rx.Observable.timer(1000) :
Rx.Observable.fromEvent(window, 'online')
});
})
.subscribe(val => console.log(val))
on / offline
107 https://goo.gl/SAmmsD
Rx.Observable.interval(1000)
.map(val => {
if (val > 5) {
throw new Error('too high');
}
return val;
})
.retryWhen(errors => {
return errors.delayWhen(() => {
const online = window.navigator.onLine;
return online ?
Rx.Observable.timer(1000) :
Rx.Observable.fromEvent(window, 'online')
});
})
.subscribe(val => console.log(val))
on / offline
108 https://goo.gl/SAmmsD
Rx.Observable.interval(1000)
.map(val => {
if (val > 5) {
throw new Error('too high');
}
return val;
})
.retryWhen(errors => {
return errors.delayWhen(() => {
const online = window.navigator.onLine;
return online ?
Rx.Observable.timer(1000) :
Rx.Observable.fromEvent(window, 'online')
});
})
.subscribe(val => console.log(val))
Websocket on / offline
const socket = Rx.Observable.webSocket('wss://echo.websocket.org')
const send = document.querySelector('#send');
socket.multiplex(() => {
console.log('connected')
return JSON.stringify({msg: 'hi'});
}, () => {
console.log('disconnected')
return JSON.strinfify({msg:'all'});
}, data => {
console.log(`sending: ${data.msg}`);
return true;
})
.retryWhen(err => {
return err
.do(val => console.log(`error with ${val}`))
.delayWhen(() => {
return window.navigator.onLine ?
Rx.Observable.timer(1000) :
Rx.Observable.fromEvent(window, 'online');
});
})
.subscribe(result => console.log(`received: ${result.msg}`));
send.onclick = () => socket.next(JSON.stringify({msg: 'wow'}));
109 https://goo.gl/FCS2ae
Websocket on / offline
const socket = Rx.Observable.webSocket('wss://echo.websocket.org')
const send = document.querySelector('#send');
socket.multiplex(() => {
console.log('connected')
return JSON.stringify({msg: 'hi'});
}, () => {
console.log('disconnected')
return JSON.strinfify({msg:'all'});
}, data => {
console.log(`sending: ${data.msg}`);
return true;
})
.retryWhen(err => {
return err
.do(val => console.log(`error with ${val}`))
.delayWhen(() => {
return window.navigator.onLine ?
Rx.Observable.timer(1000) :
Rx.Observable.fromEvent(window, 'online');
});
})
.subscribe(result => console.log(`received: ${result.msg}`));
send.onclick = () => socket.next(JSON.stringify({msg: 'wow'}));
110 https://goo.gl/FCS2ae
Websocket on / offline
const socket = Rx.Observable.webSocket('wss://echo.websocket.org')
const send = document.querySelector('#send');
socket.multiplex(() => {
console.log('connected')
return JSON.stringify({msg: 'hi'});
}, () => {
console.log('disconnected')
return JSON.strinfify({msg:'all'});
}, data => {
console.log(`sending: ${data.msg}`);
return true;
})
.retryWhen(err => {
return err
.do(val => console.log(`error with ${val}`))
.delayWhen(() => {
return window.navigator.onLine ?
Rx.Observable.timer(1000) :
Rx.Observable.fromEvent(window, 'online');
});
})
.subscribe(result => console.log(`received: ${result.msg}`));
send.onclick = () => socket.next(JSON.stringify({msg: 'wow'}));
111 https://goo.gl/FCS2ae
­Dan Abramov, @JavaScript Air 025
“Rx can solve many real world tasks.
And once you get used to it,
you can apply it pretty much everywhere.”
112
장점
• 데이터의 변환 / 흐름에만 집중 할 수 있다.
- 함수가 동기 / 비동기인지는 중요하지 않음
- Operator 는 마치 파이프 같음.
- 파이프(Operator)만 잘 연결하면, 물(데이터)은 파이프를 따라 흐른다.
- 이것이 Reactive Programming?
- 불가능하다고 생각했던 것들을 만들 수 있게 되었다.
• Observable 을 인터페이스의 중심으로
- Observable 은 거의 모든 비동기 상황을 표현할 수 있음.
- 쉬워진 새로운 기능 추가
- 쉬워진 테스트 (mocking 이 수월한 경우에...)
113
단점
• 러닝커브
- 커브 정도가 아니라 절벽 수준 😱
- 코드 + 사고방식까지 바꾸어야 함
- +200 Operators, Subject, Scheduler...
- 처음에는 무섭지만, 막상 사용하다보면 많이 사용하게 되는 건 7 ~ 8 개 정도?
• 디버깅
- 길고, 복잡한 call stack
• RxJS 의 내부 타입 / 구현을 모른다면 거의 쓸모없는 정보들 😥
• operator 를 제대로 이해하고 사용한다면 거의 볼 일이 없음
• RxJS@5 에서는 그나마 조금 줄었음
- do operator 를 활용하세요!!
• 서버 보다는 UI 개발에 더 많은 도움을 줄 수 있다고 생각함 🙏
114
Who to follow
• Erik Meijer
- creator of reactive-extensions
- 📹 One hacker way
• Matthew Podwyski
- initial creator of RxJS
• Ben Lesh
- RxJS@5 main contributor
115
• André Staltz
- RxJS@5 main contributor
- creator of cycle.js
• kris kowal
- creator of Q
- 📰 general theory of reactivity
What to see
• https://github.com/reactivex/rxjs
• 📰 official RxJS@5 doc
• 📹 💵 egghead RxJS series
- RxJS Beyond the Basics: Creating Observables from scratch
- RxJS Beyond the Basics: Operators in Depth
• 📖 learn-rxjs
116
Q & A
117

More Related Content

What's hot (20)

PDF
RxJS Evolved
trxcllnt
 
PDF
rx.js make async programming simpler
Alexander Mostovenko
 
PPTX
Luis Atencio on RxJS
Luis Atencio
 
PDF
Letswift19-clean-architecture
Jung Kim
 
PDF
Functional Reactive Programming / Compositional Event Systems
Leonardo Borges
 
PPTX
Angular2 rxjs
Christoffer Noring
 
PDF
Think Async: Asynchronous Patterns in NodeJS
Adam L Barrett
 
PDF
Concurrency Concepts in Java
Doug Hawkins
 
PPTX
ES6 in Real Life
Domenic Denicola
 
PDF
Map kit light
CocoaHeads France
 
PDF
RxJS - The Reactive extensions for JavaScript
Viliam Elischer
 
PDF
D3.js workshop
Anton Katunin
 
PDF
Writing native bindings to node.js in C++
nsm.nikhil
 
PDF
Letswift18 워크숍#1 스위프트 클린코드와 코드리뷰
Jung Kim
 
PDF
Rainer Grimm, “Functional Programming in C++11”
Platonov Sergey
 
PDF
[Let'Swift 2019] 실용적인 함수형 프로그래밍 워크샵
Wanbok Choi
 
PPTX
The State of JavaScript
Domenic Denicola
 
PDF
Oop assignment 02
MamoonKhan39
 
PDF
JavaScript 2016 for C# Developers
Rick Beerendonk
 
RxJS Evolved
trxcllnt
 
rx.js make async programming simpler
Alexander Mostovenko
 
Luis Atencio on RxJS
Luis Atencio
 
Letswift19-clean-architecture
Jung Kim
 
Functional Reactive Programming / Compositional Event Systems
Leonardo Borges
 
Angular2 rxjs
Christoffer Noring
 
Think Async: Asynchronous Patterns in NodeJS
Adam L Barrett
 
Concurrency Concepts in Java
Doug Hawkins
 
ES6 in Real Life
Domenic Denicola
 
Map kit light
CocoaHeads France
 
RxJS - The Reactive extensions for JavaScript
Viliam Elischer
 
D3.js workshop
Anton Katunin
 
Writing native bindings to node.js in C++
nsm.nikhil
 
Letswift18 워크숍#1 스위프트 클린코드와 코드리뷰
Jung Kim
 
Rainer Grimm, “Functional Programming in C++11”
Platonov Sergey
 
[Let'Swift 2019] 실용적인 함수형 프로그래밍 워크샵
Wanbok Choi
 
The State of JavaScript
Domenic Denicola
 
Oop assignment 02
MamoonKhan39
 
JavaScript 2016 for C# Developers
Rick Beerendonk
 

Viewers also liked (10)

PDF
혁신적인 웹컴포넌트 라이브러리 - Polymer
Jae Sung Park
 
PDF
Angular2 ecosystem
Kamil Lelonek
 
PDF
System webpack-jspm
Jesse Warden
 
PDF
Functional Reactive Programming With RxSwift
선협 이
 
PDF
[1B4]안드로이드 동시성_프로그래밍
NAVER D2
 
PPTX
NDC14 - Rx와 Functional Reactive Programming으로 고성능 서버 만들기
Jong Wook Kim
 
PPTX
RxJS and Reactive Programming - Modern Web UI - May 2015
Ben Lesh
 
PDF
Module, AMD, RequireJS
偉格 高
 
PDF
Ionic으로 모바일앱 만들기 #1
성일 한
 
PPTX
[124] 하이브리드 앱 개발기 김한솔
NAVER D2
 
혁신적인 웹컴포넌트 라이브러리 - Polymer
Jae Sung Park
 
Angular2 ecosystem
Kamil Lelonek
 
System webpack-jspm
Jesse Warden
 
Functional Reactive Programming With RxSwift
선협 이
 
[1B4]안드로이드 동시성_프로그래밍
NAVER D2
 
NDC14 - Rx와 Functional Reactive Programming으로 고성능 서버 만들기
Jong Wook Kim
 
RxJS and Reactive Programming - Modern Web UI - May 2015
Ben Lesh
 
Module, AMD, RequireJS
偉格 高
 
Ionic으로 모바일앱 만들기 #1
성일 한
 
[124] 하이브리드 앱 개발기 김한솔
NAVER D2
 
Ad

Similar to Compose Async with RxJS (20)

PDF
RESTful API using scalaz (3)
Yeshwanth Kumar
 
PDF
User Defined Aggregation in Apache Spark: A Love Story
Databricks
 
PDF
User Defined Aggregation in Apache Spark: A Love Story
Databricks
 
PDF
JS Fest 2019. Anjana Vakil. Serverless Bebop
JSFestUA
 
PDF
Apache Spark for Library Developers with William Benton and Erik Erlandson
Databricks
 
PDF
Refactoring to Macros with Clojure
Dmitry Buzdin
 
PPTX
Apache Spark in your likeness - low and high level customization
Bartosz Konieczny
 
PPT
Expert JavaScript tricks of the masters
Ara Pehlivanian
 
PPTX
Fact, Fiction, and FP
Brian Lonsdorf
 
PDF
Monitoring Your ISP Using InfluxDB Cloud and Raspberry Pi
InfluxData
 
PPTX
Rxjs swetugg
Christoffer Noring
 
PPTX
Rxjs marble-testing
Christoffer Noring
 
DOC
code for quiz in my sql
JOYITAKUNDU1
 
PPTX
Async Redux Actions With RxJS - React Rally 2016
Ben Lesh
 
PPTX
Rx java in action
Pratama Nur Wijaya
 
PDF
MongoDB World 2019: Life In Stitch-es
MongoDB
 
PDF
RxJava applied [JavaDay Kyiv 2016]
Igor Lozynskyi
 
PDF
Functional UIs with Java 8 and Vaadin JavaOne2014
hezamu
 
PDF
Job Queue in Golang
Bo-Yi Wu
 
PDF
Reactive Programming - ReactFoo 2020 - Aziz Khambati
Aziz Khambati
 
RESTful API using scalaz (3)
Yeshwanth Kumar
 
User Defined Aggregation in Apache Spark: A Love Story
Databricks
 
User Defined Aggregation in Apache Spark: A Love Story
Databricks
 
JS Fest 2019. Anjana Vakil. Serverless Bebop
JSFestUA
 
Apache Spark for Library Developers with William Benton and Erik Erlandson
Databricks
 
Refactoring to Macros with Clojure
Dmitry Buzdin
 
Apache Spark in your likeness - low and high level customization
Bartosz Konieczny
 
Expert JavaScript tricks of the masters
Ara Pehlivanian
 
Fact, Fiction, and FP
Brian Lonsdorf
 
Monitoring Your ISP Using InfluxDB Cloud and Raspberry Pi
InfluxData
 
Rxjs swetugg
Christoffer Noring
 
Rxjs marble-testing
Christoffer Noring
 
code for quiz in my sql
JOYITAKUNDU1
 
Async Redux Actions With RxJS - React Rally 2016
Ben Lesh
 
Rx java in action
Pratama Nur Wijaya
 
MongoDB World 2019: Life In Stitch-es
MongoDB
 
RxJava applied [JavaDay Kyiv 2016]
Igor Lozynskyi
 
Functional UIs with Java 8 and Vaadin JavaOne2014
hezamu
 
Job Queue in Golang
Bo-Yi Wu
 
Reactive Programming - ReactFoo 2020 - Aziz Khambati
Aziz Khambati
 
Ad

Recently uploaded (20)

PDF
Alarm in Android-Scheduling Timed Tasks Using AlarmManager in Android.pdf
Nabin Dhakal
 
PPTX
AEM User Group: India Chapter Kickoff Meeting
jennaf3
 
PDF
Why Businesses Are Switching to Open Source Alternatives to Crystal Reports.pdf
Varsha Nayak
 
PPTX
In From the Cold: Open Source as Part of Mainstream Software Asset Management
Shane Coughlan
 
PDF
Online Queue Management System for Public Service Offices in Nepal [Focused i...
Rishab Acharya
 
PDF
[Solution] Why Choose the VeryPDF DRM Protector Custom-Built Solution for You...
Lingwen1998
 
PDF
vMix Pro 28.0.0.42 Download vMix Registration key Bundle
kulindacore
 
PDF
Open Chain Q2 Steering Committee Meeting - 2025-06-25
Shane Coughlan
 
PDF
Download Canva Pro 2025 PC Crack Full Latest Version
bashirkhan333g
 
PDF
Unlock Efficiency with Insurance Policy Administration Systems
Insurance Tech Services
 
PDF
SciPy 2025 - Packaging a Scientific Python Project
Henry Schreiner
 
PDF
MiniTool Partition Wizard Free Crack + Full Free Download 2025
bashirkhan333g
 
PPTX
Change Common Properties in IBM SPSS Statistics Version 31.pptx
Version 1 Analytics
 
PDF
Top Agile Project Management Tools for Teams in 2025
Orangescrum
 
PDF
MiniTool Partition Wizard 12.8 Crack License Key LATEST
hashhshs786
 
PPTX
Why Businesses Are Switching to Open Source Alternatives to Crystal Reports.pptx
Varsha Nayak
 
PDF
Thread In Android-Mastering Concurrency for Responsive Apps.pdf
Nabin Dhakal
 
PDF
HiHelloHR – Simplify HR Operations for Modern Workplaces
HiHelloHR
 
PDF
Odoo CRM vs Zoho CRM: Honest Comparison 2025
Odiware Technologies Private Limited
 
PPTX
Transforming Mining & Engineering Operations with Odoo ERP | Streamline Proje...
SatishKumar2651
 
Alarm in Android-Scheduling Timed Tasks Using AlarmManager in Android.pdf
Nabin Dhakal
 
AEM User Group: India Chapter Kickoff Meeting
jennaf3
 
Why Businesses Are Switching to Open Source Alternatives to Crystal Reports.pdf
Varsha Nayak
 
In From the Cold: Open Source as Part of Mainstream Software Asset Management
Shane Coughlan
 
Online Queue Management System for Public Service Offices in Nepal [Focused i...
Rishab Acharya
 
[Solution] Why Choose the VeryPDF DRM Protector Custom-Built Solution for You...
Lingwen1998
 
vMix Pro 28.0.0.42 Download vMix Registration key Bundle
kulindacore
 
Open Chain Q2 Steering Committee Meeting - 2025-06-25
Shane Coughlan
 
Download Canva Pro 2025 PC Crack Full Latest Version
bashirkhan333g
 
Unlock Efficiency with Insurance Policy Administration Systems
Insurance Tech Services
 
SciPy 2025 - Packaging a Scientific Python Project
Henry Schreiner
 
MiniTool Partition Wizard Free Crack + Full Free Download 2025
bashirkhan333g
 
Change Common Properties in IBM SPSS Statistics Version 31.pptx
Version 1 Analytics
 
Top Agile Project Management Tools for Teams in 2025
Orangescrum
 
MiniTool Partition Wizard 12.8 Crack License Key LATEST
hashhshs786
 
Why Businesses Are Switching to Open Source Alternatives to Crystal Reports.pptx
Varsha Nayak
 
Thread In Android-Mastering Concurrency for Responsive Apps.pdf
Nabin Dhakal
 
HiHelloHR – Simplify HR Operations for Modern Workplaces
HiHelloHR
 
Odoo CRM vs Zoho CRM: Honest Comparison 2025
Odiware Technologies Private Limited
 
Transforming Mining & Engineering Operations with Odoo ERP | Streamline Proje...
SatishKumar2651
 

Compose Async with RxJS

  • 1. Compose Async with RxJS @chitacan, Riiid 1
  • 3. RxJS ? Reactive extensions library for JavaScript 3
  • 4. 시작하기 전에 [1, 2, 3, 4] .map(d => d * 2) .filter(d => d > 4) .forEach(console.log); 4
  • 5. 시작하기 전에 [1, 2, 3, 4] .map(d => d * 2) .filter(d => d > 4) .forEach(console.log); 4 > 6 > 8
  • 6. Event 를 이렇게 처리한다면 어떨까요? 5
  • 7. 시작하기 전에 [..Event{}..Event{}...Event{}...] .map(e => e.clientX * 2) .filter(x => x > 4) .forEach(updateUI); 6
  • 8. 시작하기 전에 [..Event{}..Event{}...Event{}...] .map(e => e.clientX * 2) .filter(x => x > 4) .forEach(updateUI); 7
  • 9. 시작하기 전에 [..Event{}..Event{}...Event{}...] .map(e => e.clientX * 2) .filter(x => x > 4) .forEach(updateUI); 8
  • 10. 시작하기 전에 [..Event{}..Event{}...Event{}...] .map(e => e.clientX * 2) .filter(x => x > 4) .forEach(updateUI); 9
  • 11. 시작하기 전에 [..Event{}..Event{}...Event{}...] .map(e => e.clientX * 2) .filter(x => x > 4) .forEach(updateUI); [1, 2, 3, 4] .map(d => d * 2) .filter(d => d > 4) .forEach(console.log); 10
  • 12. Array 와 Event 는 모두 Collections 11
  • 13. Event 는 데이터의 모음이고, Array 처럼 처리할 수 있다. 12
  • 14. Array 와 Event • 차이점 - array 는 각 요소를 동기적 (sync) 으로 조회할 수 있고, 끝이 있다. - event 는 각 요소를 비동기적 (async) 으로 조회할 수 있고, 끝이 없다.
 (하지만 event listening 을 취소 할 수 있다.) • 비동기적으로 생성되는 요소들을 표현하고, 원하는 시점에 취소할 수 있는 타입 13
  • 17. [ ] time collections over time 15 1....2..3..
  • 18. pull vs push [1, 2, 3].forEach(console.log); [1..2..3...].forEach(console.log); 16
  • 19. pull vs push [1, 2, 3].forEach(console.log); [1..2..3...].forEach(console.log); 16 > 1 > 2 > 3
  • 20. pull vs push [1, 2, 3].forEach(console.log); [1..2..3...].forEach(console.log); 16 > 1 > 2 > 3 > 1
  • 21. pull vs push [1, 2, 3].forEach(console.log); [1..2..3...].forEach(console.log); 16 > 1 > 2 > 3 > 1 > 2
  • 22. pull vs push [1, 2, 3].forEach(console.log); [1..2..3...].forEach(console.log); 16 > 1 > 2 > 3 > 1 > 2 > 3 // ...
  • 23. pull vs push [1, 2, 3].forEach(console.log); [1..2..3...].forEach(console.log); 16 > 1 > 2 > 3 > 1 > 2 > 3 // ... pull
  • 24. pull vs push [1, 2, 3].forEach(console.log); [1..2..3...].forEach(console.log); 16 > 1 > 2 > 3 > 1 > 2 > 3 // ... pull push
  • 25. “RxJS 는 Observable 타입을 활용해 Event를 Array 처럼 처리 할 수 있는 라이브러리” 17
  • 26. ­Ben Lesh, RxJS In-Depth @AngularConnect 2015 “RxJS is LoDash or Underscore for async” 18
  • 27. RxJS • Observable 타입 • Operators (map, filter ...) • Scheduler 19
  • 28. Observable vs Promise Promise Observable single value multiple value not lazy lazy not cancelable cancelable no completion callback completion callback 20
  • 29. single value vs multiple value • DOM / Event Emitter events (0 - N values) • Animations (cancelable) • REST API (1 value) • WebSockets (0 - N values, retry) • node.js core API (1 - N values) 21
  • 30. single value vs multiple value • DOM / Event Emitter events (0 - N values) • Animations (cancelable) • REST API (1 value) • WebSockets (0 - N values) • node.js core API (1 - N values) 22
  • 31. Promise: not lazy const p = new Promise(resolve => { setTimeout(() => { resolve(‘run'); }, 1000); console.log('started'); }); 23
  • 32. Promise: not lazy const p = new Promise(resolve => { setTimeout(() => { resolve(‘run'); }, 1000); console.log('started'); }); 24 executor
  • 33. Promise: not lazy const p = new Promise(resolve => { setTimeout(() => { resolve(‘run'); }, 1000); console.log('started'); }); 25 https://goo.gl/tK39aS
  • 34. Promise: not lazy const p = new Promise(resolve => { setTimeout(() => { resolve(‘run'); }, 1000); console.log('started'); }); 25 > "started" https://goo.gl/tK39aS
  • 35. const source = Rx.Observable.create(observer => { setTimeout(() => { observer.next('run'); }, 1000); console.log('started'); }); Observable: lazy 26
  • 36. const source = Rx.Observable.create(observer => { setTimeout(() => { observer.next('run'); }, 1000); console.log('started'); }); Observable: lazy 26 // no console output
  • 37. const source = Rx.Observable.create(observer => { setTimeout(() => { observer.next('run'); }, 1000); console.log('started'); }); source.subscribe(val => console.log(val)); Observable: lazy 27 https://goo.gl/qh24FK
  • 38. const source = Rx.Observable.create(observer => { setTimeout(() => { observer.next('run'); }, 1000); console.log('started'); }); source.subscribe(val => console.log(val)); Observable: lazy 27 > "started" https://goo.gl/qh24FK
  • 39. const source = Rx.Observable.create(observer => { setTimeout(() => { observer.next('run'); }, 1000); console.log('started'); }); source.subscribe(val => console.log(val)); Observable: lazy 27 > "started" > "run" https://goo.gl/qh24FK
  • 40. Promise: not cancelable const p = new Promise(resolve => { var val = 0; setInterval(() => { console.log(`val: ${val}`) resolve(++val); }, 1000); console.log('started'); }); p.then(val => console.log(`result: ${val}`)); 28
  • 41. Promise: not cancelable const p = new Promise(resolve => { var val = 0; setInterval(() => { console.log(`val: ${val}`) resolve(++val); }, 1000); console.log('started'); }); p.then(val => console.log(`result: ${val}`)); 28 > "started"
  • 42. Promise: not cancelable const p = new Promise(resolve => { var val = 0; setInterval(() => { console.log(`val: ${val}`) resolve(++val); }, 1000); console.log('started'); }); p.then(val => console.log(`result: ${val}`)); 28 > "started" > "val: 0"
  • 43. Promise: not cancelable const p = new Promise(resolve => { var val = 0; setInterval(() => { console.log(`val: ${val}`) resolve(++val); }, 1000); console.log('started'); }); p.then(val => console.log(`result: ${val}`)); 28 > "started" > "val: 0" > "result: 0” // promise result > "val: 1" > "val: 2" > "val: 3" // ??????????
  • 44. Observable: cancelable const source = Rx.Observable.create(observer => { var val = 0; const id = setInterval(() => { console.log(`val: ${val}`) observer.next(++val); }, 1000); console.log('started'); return () => { clearInterval(id); console.log('cancelled'); }; }); const subscription = source.subscribe(val => console.log(`result: ${val}`)); setTimeout(() => subscription.unsubscribe(), 5000); 29 https://goo.gl/qJ1zRi
  • 45. Observable: cancelable const source = Rx.Observable.create(observer => { var val = 0; const id = setInterval(() => { console.log(`val: ${val}`) observer.next(++val); }, 1000); console.log('started'); return () => { clearInterval(id); console.log('cancelled'); }; }); const subscription = source.subscribe(val => console.log(`result: ${val}`)); setTimeout(() => subscription.unsubscribe(), 5000); > "started" > "val: 0" > "result: 0" > "val: 1" > "result: 1" // ... 30 https://goo.gl/qJ1zRi
  • 46. Observable: cancelable const source = Rx.Observable.create(observer => { var val = 0; const id = setInterval(() => { console.log(`val: ${val}`) observer.next(++val); }, 1000); console.log('started'); return () => { clearInterval(id); console.log('cancelled'); }; }); const subscription = source.subscribe(val => console.log(`result: ${val}`)); setTimeout(() => subscription.unsubscribe(), 5000); > "started" > "val: 0" > "result: 0" > "val: 1" > "result: 1" // ... 30 teardown logic https://goo.gl/qJ1zRi
  • 47. Observable: cancelable const source = Rx.Observable.create(observer => { var val = 0; const id = setInterval(() => { console.log(`val: ${val}`) observer.next(++val); }, 1000); console.log('started'); return () => { clearInterval(id); console.log('cancelled'); }; }); const subscription = source.subscribe(val => console.log(`result: ${val}`)); setTimeout(() => subscription.unsubscribe(), 5000); > "started" > "val: 0" > "result: 0" > "val: 1" > "result: 1" // ... 30 > "cancelled" teardown logic https://goo.gl/qJ1zRi
  • 50. Operators • observable 타입을 변환 / 조합할 수 있음 • RxJS@5 기준 +200개 • 종류 - creation - transformation - filtering - composition - error - ... 33
  • 51. creation • create • from • fromEvent • fromPromise • interval 34
  • 52. from const source = Rx.Observable.from([1,2,3,4,5]) source.subscribe(val => console.log(val)); > 1 > 2 > 3 > 4 > 5 35
  • 53. Array function vs RxJS const source = [1,2,3,4,5]; const result = source .filter(d => d % 2 === 0) .map(d => d + '!') .reduce((p, d) => p + d) console.log(result); > "2!4!" 36
  • 54. Array function vs RxJS const source =[1,2,3,4,5]; const result = source .filter((d, i, arr) => { console.log(`filter: ${d}`); return d % 2 === 0; }) .map((d, i, arr) => { console.log(`map: ${d}`); return d + '!'; }) .reduce((p, d, i, arr) => { console.log(`reduce: ${d}`); return p + d; }) console.log(result); > "filter: 1" > "filter: 2" > "filter: 3" > "filter: 4" > "filter: 5" > "map: 2" > "map: 4" > "reduce: 2!" > "reduce: 4!" > "2!4!" 37 https://goo.gl/ma2vxb
  • 55. Array function vs RxJS const source = Rx.Observable.from[1,2,3,4,5]; source .filter(d => d % 2 === 0) .map(d => d + '!') .reduce((p, d) => p + d) .subscribe(result => console.log(result)); > "2!4!" 38
  • 56. Array function vs RxJS const source = Rx.Observable.from([1,2,3,4,5]); source .filter(d => { console.log(`filter: ${d}`); return d % 2 === 0; }) .map(d => { console.log(`map: ${d}`); return d + '!'; }) .reduce((p, d) => { console.log(`reduce: ${d}`); return p + d; }, '') .subscribe(result => console.log(result)); > "filter: 1" > "filter: 2" > "map: 2" > "reduce: 2!" > "filter: 3" > "filter: 4" > "map: 4" > "reduce: 4!" > "filter: 5" > "2!4!" 39 https://goo.gl/5dtLXF
  • 57. Array function vs RxJS const source = Rx.Observable.from([1,2,3,4,5]); source .filter(d => { console.log(`filter: ${d}`); return d % 2 === 0; }) .map(d => { console.log(`map: ${d}`); return d + '!'; }) .reduce((p, d) => { console.log(`reduce: ${d}`); return p + d; }, '') .subscribe(result => console.log(result)); > "filter: 1" > "filter: 2" > "map: 2" > "reduce: 2!" > "filter: 3" > "filter: 4" > "map: 4" > "reduce: 4!" > "filter: 5" > "2!4!" 40 https://goo.gl/5dtLXF
  • 62. fromEvent const source = Rx.Observable.fromEvent(document, ‘click'); source.subscribe(val => console.log(val)); 43
  • 63. fromEvent const source = Rx.Observable.fromEvent(document, 'click'); .map(event => event.x) .filter(x => x > 100); source.subscribe(val => console.log(val)); 44
  • 64. fromPromise const request = axios.get(URL); const source = Rx.Observable.fromPromise(request); .map(res => res.data); source.subscribe(val => console.log(val)); 45
  • 65. interval const source = Rx.Observable.interval(1000); source.subscribe(val => { console.log(val); }, err => { console.error(err); }, () => { console.log('completed'); }); 46 https://goo.gl/ybHNDZ
  • 66. interval const source = Rx.Observable.interval(1000); source.subscribe(val => { console.log(val); }, err => { console.error(err); }, () => { console.log('completed'); }); 46 > 0 > 1 > 2 > 3 // ... https://goo.gl/ybHNDZ
  • 67. interval const source = Rx.Observable.interval(1000); source.subscribe(val => { console.log(val); }, err => { console.error(err); }, () => { console.log('completed'); }); 46 > 0 > 1 > 2 > 3 // ... // "completed" will not be printed https://goo.gl/ybHNDZ
  • 69. map const source = Rx.Observable.from([1,2,3,4]) .map(d => d * 10); source.subscribe(val => console.log(val)); > 10 > 20 > 30 > 40 48
  • 70. mergeMap (flatMap) _.flatMap([1, 2], d => [d, d]); // [[1, 1], [2, 2]] > [1, 1, 2, 2] _.flatten([[1, 1], [2, 2]]); > [1, 1, 2, 2] 49
  • 71. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  • 72. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  • 73. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  • 74. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  • 75. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  • 76. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  • 77. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  • 78. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  • 79. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  • 80. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  • 81. mergeMap (flatMap) const source = requestUser$() .mergeMap(res => { const {data: {userId}} = res; return requestPosts$({userId}); }); source.subscribe(res => console.log(res)); 51 https://goo.gl/SHgVZ4
  • 82. mergeMap (flatMap) const source = requestUser$() .mergeMap(res => { const {data: {userId}} = res; return requestPosts$({userId}); }); source.subscribe(res => console.log(res)); 52 https://goo.gl/SHgVZ4
  • 83. mergeMap (flatMap) const source = requestUser$() // [.....user] .mergeMap(res => { const {data: {userId}} = res; return requestPosts$({userId}); // [....[..posts]] }); source.subscribe(res => console.log(res)); // [.....posts] 53 https://goo.gl/SHgVZ4
  • 84. mergeMap (flatMap) const source = requestUser$() .mergeMap(res => { const {data: {userId}} = res; return axios.get(`${URL}/posts`, {params: {id: userId}}); }); source.subscribe(res => console.log(res.data)); 54 https://goo.gl/FKaQLr
  • 86. filter const source = Rx.Observable.from([1,2,3,4]) .filter(d => d > 3); source.subscribe(val => console.log(val)); > 4 56
  • 87. take const source = Rx.Observable.interval(1000) .take(3); source.subscribe(val => { console.log(val); }, err => { console.error(err); }, () => { console.log('completed'); }); 57 https://goo.gl/NkGcDo
  • 88. take const source = Rx.Observable.interval(1000) .take(3); source.subscribe(val => { console.log(val); }, err => { console.error(err); }, () => { console.log('completed'); }); 57 > 0 > 1 > 2 > "completed" https://goo.gl/NkGcDo
  • 94. takeUntil const source = Rx.Observable.interval(1000) .takeUntil(Rx.Observable.timer(3500)); source.subscribe(val => { console.log(val); }, err => { console.error(err); }, () => { console.log('completed'); }); 59 https://goo.gl/ZaXJFS
  • 95. takeUntil const source = Rx.Observable.interval(1000) .takeUntil(Rx.Observable.timer(3500)); source.subscribe(val => { console.log(val); }, err => { console.error(err); }, () => { console.log('completed'); }); 60 https://goo.gl/ZaXJFS
  • 96. takeUntil const source = Rx.Observable.interval(1000) .takeUntil(Rx.Observable.timer(3500)); source.subscribe(val => { console.log(val); }, err => { console.error(err); }, () => { console.log('completed'); }); 61 https://goo.gl/ZaXJFS
  • 97. takeUntil const source = Rx.Observable.interval(1000) .takeUntil(Rx.Observable.timer(3500)); source.subscribe(val => { console.log(val); }, err => { console.error(err); }, () => { console.log('completed'); }); 61 > 0 > 1 > 2 > "completed" https://goo.gl/ZaXJFS
  • 99. combineLatest combineLatest( [...0...1...2...3...4...], // source1 [.......@], // source2 [...........#] // source3 ) [ ...........[2, @, #]...[3, @, #]...[4, @, #]... ] 63
  • 100. combineLatest combineLatest( [...0...1...2...3...4...], // source1 [.......@], // source2 [...........#] // source3 ) [ ...........[2, @, #]...[3, @, #]...[4, @, #]... ] 63
  • 101. combineLatest combineLatest( [...0...1...2...3...4...], // source1 [.......@], // source2 [...........#] // source3 ) [ ...........[2, @, #]...[3, @, #]...[4, @, #]... ] 63
  • 102. combineLatest combineLatest( [...0...1...2...3...4...], // source1 [.......@], // source2 [...........#] // source3 ) [ ...........[2, @, #]...[3, @, #]...[4, @, #]... ] 63 @
  • 103. combineLatest combineLatest( [...0...1...2...3...4...], // source1 [.......@], // source2 [...........#] // source3 ) [ ...........[2, @, #]...[3, @, #]...[4, @, #]... ] 63 @ @ #
  • 104. combineLatest combineLatest( [...0...1...2...3...4...], // source1 [.......@], // source2 [...........#] // source3 ) [ ...........[2, @, #]...[3, @, #]...[4, @, #]... ] 63 @ @ @ # #
  • 105. combineLatest const source1 = Rx.Observable.interval(1000); const source2 = Rx.Observable.timer(2000).mapTo(‘@'); const source3 = Rx.Observable.timer(3000).mapTo(‘#’); Rx.Observable.combineLatest(source1, source2, source3) .subscribe(result => console.log(result)); 64 https://goo.gl/t9vpLF
  • 106. combineLatest const source1 = Rx.Observable.interval(1000); const source2 = Rx.Observable.timer(2000).mapTo(‘@'); const source3 = Rx.Observable.timer(3000).mapTo(‘#’); Rx.Observable.combineLatest(source1, source2, source3) .subscribe(result => console.log(result)); 64 > [2, “@", “#"] > [3, “@", “#"] > [4, “@", “#"] // ... https://goo.gl/t9vpLF
  • 107. zip zip( [...0...1...2...3...4...5], // source1 [.....0.....1.....2...] // source2 ) [ ....[0, 0]....[1, 1]....[2, 2]... ] 65
  • 108. zip zip( [...0...1...2...3...4...5], // source1 [.....0.....1.....2...] // source2 ) [ ....[0, 0]....[1, 1]....[2, 2]... ] 65
  • 109. zip zip( [...0...1...2...3...4...5], // source1 [.....0.....1.....2...] // source2 ) [ ....[0, 0]....[1, 1]....[2, 2]... ] 65
  • 110. zip zip( [...0...1...2...3...4...5], // source1 [.....0.....1.....2...] // source2 ) [ ....[0, 0]....[1, 1]....[2, 2]... ] 65
  • 111. zip const source1 = Rx.Observable.interval(1000); const source2 = Rx.Observable.interval(2000); Rx.Observable.zip(source1, source2) .subscribe(result => console.log(result)); 66 https://goo.gl/wmQW7m
  • 112. zip const source1 = Rx.Observable.interval(1000); const source2 = Rx.Observable.interval(2000); Rx.Observable.zip(source1, source2) .subscribe(result => console.log(result)); 66 > [0, 0] > [1, 1] > [2, 2] // ... https://goo.gl/wmQW7m
  • 113. merge const source1 = Rx.Observable.interval(1000); const source2 = Rx.Observable.timer(2500).mapTo('source2'); Rx.Observable.merge(source1, source2) .subscribe(result => console.log(result)); > 0 > 1 > 2 > "source2" > 3 // ... 67 https://goo.gl/OqaQf9
  • 114. error • retry 🌟 • retryWhen 🌟 68
  • 115. retry Rx.Observable.interval(1000) .map(val => { if (val > 2) { throw new Error('over 2'); } return val; }) .retry(2) .subscribe(result => { console.log(result); }, err => { console.error(`error: ${error.message}`); }); 69 https://goo.gl/5zFqOl
  • 116. retry Rx.Observable.interval(1000) .map(val => { if (val > 2) { throw new Error('over 2'); } return val; }) .retry(2) .subscribe(result => { console.log(result); }, err => { console.error(`error: ${error.message}`); }); 70 https://goo.gl/5zFqOl
  • 117. retry Rx.Observable.interval(1000) .map(val => { if (val > 2) { throw new Error('over 2'); } return val; }) .retry(2) .subscribe(result => { console.log(result); }, err => { console.error(`error: $error.message}`); }); 71 https://goo.gl/5zFqOl
  • 118. retry Rx.Observable.interval(1000) .map(val => { if (val > 2) { throw new Error('over 2'); } return val; }) .retry(2) .subscribe(result => { console.log(result); }, err => { console.error(`error: $error.message}`); }); 71 https://goo.gl/5zFqOl > 0 > 1 > 2 > “error: over 2" > 0 > 1 > 2 > 0 > 1 > 2
  • 133. retryWhen 73 Rx.Observable.interval(1000) .map(val => { if (val > 2) { throw new Error('over 2'); } return val; }) .retryWhen(error => { return error .do(err => console.error(err.message)) .delay(2000); }) .subscribe(result => console.log(result)); https://goo.gl/9bhNHh
  • 134. retryWhen 74 Rx.Observable.interval(1000) .map(val => { if (val > 2) { throw new Error('over 2'); } return val; }) .retryWhen(error => { return error .do(err => console.error(err.message)) .delay(2000); }) .subscribe(result => console.log(result)); https://goo.gl/9bhNHh
  • 135. retryWhen 75 Rx.Observable.interval(1000) .map(val => { if (val > 2) { throw new Error('over 2'); } return val; }) .retryWhen(error => { return error .do(err => console.error(err.message)) .delay(2000); }) .subscribe(result => console.log(result)); https://goo.gl/9bhNHh
  • 136. retryWhen 75 Rx.Observable.interval(1000) .map(val => { if (val > 2) { throw new Error('over 2'); } return val; }) .retryWhen(error => { return error .do(err => console.error(err.message)) .delay(2000); }) .subscribe(result => console.log(result)); https://goo.gl/9bhNHh > "over 2" > 0 > 1 > 2 > "over 2" > 0 > 1 > 2 > "over 2" > 0 > 1 > 2 // ...
  • 137. operators • switchMap 🌟 • concatMap 🌟 • publish • share • skip • bufferCount • bufferTime • Throttle • debounce 76 • concat • catch • bindNodeCallback • +others
  • 138. Compose async with RxJS • 여러 이벤트를 조합해야 할 때 (Drag & Drop) • 다수의 비동기 함수를 조합하는 상황 (Cache or Db, AWS Lambda) • 재시도가 필요한 경우 (on / offline) 77
  • 139. Drag & Drop • mousedown, mousemove, mouseup 3가지 유저 이벤트를 적절 하게 조합해야 함 - mousedown 이벤트가 발생하면 현재 엘리먼트의 초기 위치를 저장 - mousemove 이벤트가 발생하면 초기 위치 + 변한 위치를 계산해 새로운 위치로 엘리먼트를 옮김 - 언제까지? mouseup 이벤트가 발생할 때까지 78
  • 140. Drag & Drop const target = document.getElementById('target'); const down = Rx.Observable.fromEvent(target, 'mousedown'); const move = Rx.Observable.fromEvent(document, 'mousemove'); const up = Rx.Observable.fromEvent(target, 'mouseup'); const drag = down.flatMap(downEvent => { const downX = downEvent.clientX; const downY = downEvent.clientY; const downLeft = parseInt(downEvent.target.style.left, 10) || 0; const downTop = parseInt(downEvent.target.style.top, 10) || 0; return move.map(moveEvent => { moveEvent.preventDefault(); return { left: moveEvent.clientX + (downLeft - downX), top : moveEvent.clientY + (downTop - downY) }; }) .takeUntil(up); }); drag.subscribe(result => { target.style.top = result.top + 'px'; target.style.left = result.left + 'px'; }); 79 https://goo.gl/nyeK8l
  • 141. Drag & Drop const target = document.getElementById('target'); const down = Rx.Observable.fromEvent(target, 'mousedown'); const move = Rx.Observable.fromEvent(document, 'mousemove'); const up = Rx.Observable.fromEvent(target, 'mouseup'); const drag = down.flatMap(downEvent => { const downX = downEvent.clientX; const downY = downEvent.clientY; const downLeft = parseInt(downEvent.target.style.left, 10) || 0; const downTop = parseInt(downEvent.target.style.top, 10) || 0; return move.map(moveEvent => { moveEvent.preventDefault(); return { left: moveEvent.clientX + (downLeft - downX), top : moveEvent.clientY + (downTop - downY) }; }) .takeUntil(up); }); drag.subscribe(result => { target.style.top = result.top + 'px'; target.style.left = result.left + 'px'; }); 80 https://goo.gl/nyeK8l
  • 142. Drag & Drop const target = document.getElementById('target'); const down = Rx.Observable.fromEvent(target, 'mousedown'); const move = Rx.Observable.fromEvent(document, 'mousemove'); const up = Rx.Observable.fromEvent(target, 'mouseup'); const drag = down.flatMap(downEvent => { const downX = downEvent.clientX; const downY = downEvent.clientY; const downLeft = parseInt(downEvent.target.style.left, 10) || 0; const downTop = parseInt(downEvent.target.style.top, 10) || 0; return move.map(moveEvent => { moveEvent.preventDefault(); return { left: moveEvent.clientX + (downLeft - downX), top : moveEvent.clientY + (downTop - downY) }; }) .takeUntil(up); }); drag.subscribe(result => { target.style.top = result.top + 'px'; target.style.left = result.left + 'px'; }); 81 https://goo.gl/nyeK8l
  • 143. Drag & Drop const target = document.getElementById('target'); const down = Rx.Observable.fromEvent(target, 'mousedown'); const move = Rx.Observable.fromEvent(document, 'mousemove'); const up = Rx.Observable.fromEvent(target, 'mouseup'); const drag = down.flatMap(downEvent => { const downX = downEvent.clientX; const downY = downEvent.clientY; const downLeft = parseInt(downEvent.target.style.left, 10) || 0; const downTop = parseInt(downEvent.target.style.top, 10) || 0; return move.map(moveEvent => { moveEvent.preventDefault(); return { left: moveEvent.clientX + (downLeft - downX), top : moveEvent.clientY + (downTop - downY) }; }) .takeUntil(up); }); drag.subscribe(result => { target.style.top = result.top + 'px'; target.style.left = result.left + 'px'; }); 82 https://goo.gl/nyeK8l
  • 144. Drag & Drop const target = document.getElementById('target'); const down = Rx.Observable.fromEvent(target, 'mousedown'); const move = Rx.Observable.fromEvent(document, 'mousemove'); const up = Rx.Observable.fromEvent(target, 'mouseup'); const drag = down.flatMap(downEvent => { const downX = downEvent.clientX; const downY = downEvent.clientY; const downLeft = parseInt(downEvent.target.style.left, 10) || 0; const downTop = parseInt(downEvent.target.style.top, 10) || 0; return move.map(moveEvent => { moveEvent.preventDefault(); return { left: moveEvent.clientX + (downLeft - downX), top : moveEvent.clientY + (downTop - downY) }; }) .takeUntil(up); }); drag.subscribe(result => { target.style.top = result.top + 'px'; target.style.left = result.left + 'px'; }); 83 https://goo.gl/nyeK8l
  • 145. Drag & Drop const target = document.getElementById('target'); const down = Rx.Observable.fromEvent(target, 'mousedown'); const move = Rx.Observable.fromEvent(document, 'mousemove'); const up = Rx.Observable.fromEvent(target, 'mouseup'); const drag = down.flatMap(downEvent => { const downX = downEvent.clientX; const downY = downEvent.clientY; const downLeft = parseInt(downEvent.target.style.left, 10) || 0; const downTop = parseInt(downEvent.target.style.top, 10) || 0; return move.map(moveEvent => { moveEvent.preventDefault(); return { left: moveEvent.clientX + (downLeft - downX), top : moveEvent.clientY + (downTop - downY) }; }) .takeUntil(up); }); drag.subscribe(result => { target.style.top = result.top + 'px'; target.style.left = result.left + 'px'; }); 84 https://goo.gl/nyeK8l
  • 146. Cache or DB 85 • cache 또는 DB 에서 데이터를 가져오는 상황 - getFromCache$(), getFromDB$() - 둘 중에 먼저 도착한 데이터만 사용하고 싶다. - 나머지 요청은 취소되어야 함 - getFromOtherServer$() - 도착한 데이터의 값을 사용해 다른 요청 수행
  • 147. Cache or Db Rx.Observable.merge(getFromCache$(), getFromDB$()) .take(1) .flatMap(data => getFromOtherServer$(data.id)) .subscribe(result => { res.json(result) }, err => { res.status(500).send(err.message) }); 86
  • 148. Cache or Db Rx.Observable.merge(getFromCache$(), getFromDB$()) .take(1) .flatMap(data => getFromOtherServer$(data.id)) .subscribe(result => { res.json(result) }, err => { res.status(500).send(err.message) }); 87
  • 149. Cache or Db Rx.Observable.merge(getFromCache$(), getFromDB$()) .take(1) .flatMap(data => getFromOtherServer$(data.id)) .subscribe(result => { res.json(result) }, err => { res.status(500).send(err.message) }); 88
  • 150. AWS Lambda 89 • 매일 아침에 태스크 큐를 확인해서 실패한 태스크 워커가 있으면, 슬랙을 통해 알림을 받고 싶다.
  • 151. AWS Lambda 90 • Task function - getTasks$() - 태스크 큐에 쌓여있는 태스크 정보 가져오기 - checkNotified$() - renderTaskCount$() - 실패한 태스크가 존재하면 vega-renderer function 호출 • vega-renderer function - upload$() - 렌더링된 이미지를 s3 에 저장 - post$() - 저장된 이미지 경로와 함께 슬랙에 알림 전송
  • 152. Task function export default (e, ctx, cb) => { const tasks$ = getTasks$(); const notified$ = checkNotified$(); combineLatest(tasks$, notified$, (tasks, notified) => { if (notified) { return O.return('already notified'); } else if (tasks.length === 0) { return O.return('queue empty'); } return renderTaskCount$(tasks); }) .concatAll() .subscribe(result => { ctx.callbackWaitsForEmptyEventLoop = false; cb(null, result); }, err => { ctx.callbackWaitsForEmptyEventLoop = false; cb(err); }); }; 91
  • 153. Task function export default (e, ctx, cb) => { const tasks$ = getTasks$(); const notified$ = checkNotified$(); combineLatest(tasks$, notified$, (tasks, notified) => { if (notified) { return O.return('already notified'); } else if (tasks.length === 0) { return O.return('queue empty'); } return renderTaskCount$(tasks); }) .concatAll() .subscribe(result => { ctx.callbackWaitsForEmptyEventLoop = false; cb(null, result); }, err => { ctx.callbackWaitsForEmptyEventLoop = false; cb(err); }); }; 92
  • 154. Task function export default (e, ctx, cb) => { const tasks$ = getTasks$(); const notified$ = checkNotified$(); combineLatest(tasks$, notified$, (tasks, notified) => { if (notified) { return O.return('already notified'); } else if (tasks.length === 0) { return O.return('queue empty'); } return renderTaskCount$(tasks); }) .concatAll() .subscribe(result => { ctx.callbackWaitsForEmptyEventLoop = false; cb(null, result); }, err => { ctx.callbackWaitsForEmptyEventLoop = false; cb(err); }); }; 93
  • 155. Task function export default (e, ctx, cb) => { const tasks$ = getTasks$(); const notified$ = checkNotified$(); combineLatest(tasks$, notified$, (tasks, notified) => { if (notified) { return O.return('already notified'); } else if (tasks.count === 0) { return O.return('queue empty'); } return renderTaskCount$(tasks); }) .concatAll() .subscribe(result => { ctx.callbackWaitsForEmptyEventLoop = false; cb(null, result); }, err => { ctx.callbackWaitsForEmptyEventLoop = false; cb(err); }); }; 94
  • 156. Task function 95 const lambda = new Aws.Lambda(); const invoke$ = (FunctionName, Payload, InvocationType = 'RequestResponse') => { return Rx.Observable.bindNodeCallback(lambda.invoke, lambda)({ FunctionName, Payload, InvocationType }); }; export const renderTaskCount$ = values => { return invoke$('vega-renderer_png', JSON.stringify({ isLite: true, spec: Object.assign(TASK_SPEC, {data: {values}}) })) .map(res => JSON.parse(res.Payload)); };
  • 157. Task function 96 const lambda = new Aws.Lambda(); const invoke$ = (FunctionName, Payload, InvocationType = 'RequestResponse') => { return Rx.Observable.bindNodeCallback(lambda.invoke, lambda)({ FunctionName, Payload, InvocationType }); }; export const renderTaskCount$ = values => { return invoke$('vega-renderer_png', JSON.stringify({ isLite: true, spec: Object.assign(TASK_SPEC, {data: {values}}) })) .map(res => JSON.parse(res.Payload)); };
  • 158. Task function 97 const lambda = new Aws.Lambda(); const invoke$ = (FunctionName, Payload, InvocationType = 'RequestResponse') => { return Rx.Observable.bindNodeCallback(lambda.invoke, lambda)({ FunctionName, Payload, InvocationType }); }; export const renderTaskCount$ = values => { return invoke$('vega-renderer_png', JSON.stringify({ isLite: true, spec: Object.assign(TASK_SPEC, {data: {values}}) })) .map(res => JSON.parse(res.Payload)); };
  • 159. vega-renderer function 98 const s3 = new Aws.S3(); const upload$ = Rx.Observable.bindNodeCallback(s3.upload, s3); export default (event, ctx, cb) => { const {spec, isLite} = event; const filename = hash(spec); spec$(spec, isLite) .map(chart => png(chart, filename)) .flatMap(image => upload$(image)) .flatMap(data => post$(event, data.Location)) .subscribe(result => { cb(null, result); }, err => { console.log(err); cb(err); }); };
  • 160. vega-renderer function 99 const s3 = new Aws.S3(); const upload$ = Rx.Observable.bindNodeCallback(s3.upload, s3); export default (event, ctx, cb) => { const {spec, isLite} = event; const filename = hash(spec); spec$(spec, isLite) .map(chart => png(chart, filename)) .flatMap(image => upload$(image)) .flatMap(data => post$(event, data.Location)) .subscribe(result => { cb(null, result); }, err => { console.log(err); cb(err); }); };
  • 161. vega-renderer function 100 const s3 = new Aws.S3(); const upload$ = Rx.Observable.bindNodeCallback(s3.upload, s3); export default (event, ctx, cb) => { const {spec, isLite} = event; const filename = hash(spec); spec$(spec, isLite) .map(chart => png(chart, filename)) .flatMap(image => upload$(image)) .flatMap(data => post$(event, data.Location)) .subscribe(result => { cb(null, result); }, err => { console.log(err); cb(err); }); };
  • 162. vega-renderer function 101 const s3 = new Aws.S3(); const upload$ = Rx.Observable.bindNodeCallback(s3.upload, s3); export default (event, ctx, cb) => { const {spec, isLite} = event; const filename = hash(spec); spec$(spec, isLite) .map(chart => png(chart, filename)) .flatMap(image => upload$(image)) .flatMap(data => post$(event, data.Location)) .subscribe(result => { cb(null, result); }, err => { console.log(err); cb(err); }); };
  • 163. vega-renderer function 102 const s3 = new Aws.S3(); const upload$ = Rx.Observable.bindNodeCallback(s3.upload, s3); export default (event, ctx, cb) => { const {spec, isLite} = event; const filename = hash(spec); spec$(spec, isLite) .map(chart => png(chart, filename)) .flatMap(image => upload$(image)) .flatMap(data => post$(event, data.Location)) .subscribe(result => { cb(null, result); }, err => { console.log(err); cb(err); }); };
  • 164. 103
  • 165. on / offline 104 • 에러가 발생했을때 - online 상태인 경우, 1초 뒤에 재시도 - offline 상태인 경우, online 상태가 되면 재시도
  • 166. on / offline Rx.Observable.interval(1000) .map(val => { if (val > 5) { throw new Error('too high'); } return val; }) .retryWhen(errors => { return errors.delayWhen(() => { const online = window.navigator.onLine; return online ? Rx.Observable.timer(1000) : Rx.Observable.fromEvent(window, 'online') }); }) .subscribe(val => console.log(val)) 105 https://goo.gl/SAmmsD
  • 167. on / offline 106 https://goo.gl/SAmmsD Rx.Observable.interval(1000) .map(val => { if (val > 5) { throw new Error('too high'); } return val; }) .retryWhen(errors => { return errors.delayWhen(() => { const online = window.navigator.onLine; return online ? Rx.Observable.timer(1000) : Rx.Observable.fromEvent(window, 'online') }); }) .subscribe(val => console.log(val))
  • 168. on / offline 107 https://goo.gl/SAmmsD Rx.Observable.interval(1000) .map(val => { if (val > 5) { throw new Error('too high'); } return val; }) .retryWhen(errors => { return errors.delayWhen(() => { const online = window.navigator.onLine; return online ? Rx.Observable.timer(1000) : Rx.Observable.fromEvent(window, 'online') }); }) .subscribe(val => console.log(val))
  • 169. on / offline 108 https://goo.gl/SAmmsD Rx.Observable.interval(1000) .map(val => { if (val > 5) { throw new Error('too high'); } return val; }) .retryWhen(errors => { return errors.delayWhen(() => { const online = window.navigator.onLine; return online ? Rx.Observable.timer(1000) : Rx.Observable.fromEvent(window, 'online') }); }) .subscribe(val => console.log(val))
  • 170. Websocket on / offline const socket = Rx.Observable.webSocket('wss://echo.websocket.org') const send = document.querySelector('#send'); socket.multiplex(() => { console.log('connected') return JSON.stringify({msg: 'hi'}); }, () => { console.log('disconnected') return JSON.strinfify({msg:'all'}); }, data => { console.log(`sending: ${data.msg}`); return true; }) .retryWhen(err => { return err .do(val => console.log(`error with ${val}`)) .delayWhen(() => { return window.navigator.onLine ? Rx.Observable.timer(1000) : Rx.Observable.fromEvent(window, 'online'); }); }) .subscribe(result => console.log(`received: ${result.msg}`)); send.onclick = () => socket.next(JSON.stringify({msg: 'wow'})); 109 https://goo.gl/FCS2ae
  • 171. Websocket on / offline const socket = Rx.Observable.webSocket('wss://echo.websocket.org') const send = document.querySelector('#send'); socket.multiplex(() => { console.log('connected') return JSON.stringify({msg: 'hi'}); }, () => { console.log('disconnected') return JSON.strinfify({msg:'all'}); }, data => { console.log(`sending: ${data.msg}`); return true; }) .retryWhen(err => { return err .do(val => console.log(`error with ${val}`)) .delayWhen(() => { return window.navigator.onLine ? Rx.Observable.timer(1000) : Rx.Observable.fromEvent(window, 'online'); }); }) .subscribe(result => console.log(`received: ${result.msg}`)); send.onclick = () => socket.next(JSON.stringify({msg: 'wow'})); 110 https://goo.gl/FCS2ae
  • 172. Websocket on / offline const socket = Rx.Observable.webSocket('wss://echo.websocket.org') const send = document.querySelector('#send'); socket.multiplex(() => { console.log('connected') return JSON.stringify({msg: 'hi'}); }, () => { console.log('disconnected') return JSON.strinfify({msg:'all'}); }, data => { console.log(`sending: ${data.msg}`); return true; }) .retryWhen(err => { return err .do(val => console.log(`error with ${val}`)) .delayWhen(() => { return window.navigator.onLine ? Rx.Observable.timer(1000) : Rx.Observable.fromEvent(window, 'online'); }); }) .subscribe(result => console.log(`received: ${result.msg}`)); send.onclick = () => socket.next(JSON.stringify({msg: 'wow'})); 111 https://goo.gl/FCS2ae
  • 173. ­Dan Abramov, @JavaScript Air 025 “Rx can solve many real world tasks. And once you get used to it, you can apply it pretty much everywhere.” 112
  • 174. 장점 • 데이터의 변환 / 흐름에만 집중 할 수 있다. - 함수가 동기 / 비동기인지는 중요하지 않음 - Operator 는 마치 파이프 같음. - 파이프(Operator)만 잘 연결하면, 물(데이터)은 파이프를 따라 흐른다. - 이것이 Reactive Programming? - 불가능하다고 생각했던 것들을 만들 수 있게 되었다. • Observable 을 인터페이스의 중심으로 - Observable 은 거의 모든 비동기 상황을 표현할 수 있음. - 쉬워진 새로운 기능 추가 - 쉬워진 테스트 (mocking 이 수월한 경우에...) 113
  • 175. 단점 • 러닝커브 - 커브 정도가 아니라 절벽 수준 😱 - 코드 + 사고방식까지 바꾸어야 함 - +200 Operators, Subject, Scheduler... - 처음에는 무섭지만, 막상 사용하다보면 많이 사용하게 되는 건 7 ~ 8 개 정도? • 디버깅 - 길고, 복잡한 call stack • RxJS 의 내부 타입 / 구현을 모른다면 거의 쓸모없는 정보들 😥 • operator 를 제대로 이해하고 사용한다면 거의 볼 일이 없음 • RxJS@5 에서는 그나마 조금 줄었음 - do operator 를 활용하세요!! • 서버 보다는 UI 개발에 더 많은 도움을 줄 수 있다고 생각함 🙏 114
  • 176. Who to follow • Erik Meijer - creator of reactive-extensions - 📹 One hacker way • Matthew Podwyski - initial creator of RxJS • Ben Lesh - RxJS@5 main contributor 115 • André Staltz - RxJS@5 main contributor - creator of cycle.js • kris kowal - creator of Q - 📰 general theory of reactivity
  • 177. What to see • https://github.com/reactivex/rxjs • 📰 official RxJS@5 doc • 📹 💵 egghead RxJS series - RxJS Beyond the Basics: Creating Observables from scratch - RxJS Beyond the Basics: Operators in Depth • 📖 learn-rxjs 116