SlideShare uma empresa Scribd logo
1 de 178
Baixar para ler offline
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

Mais conteúdo relacionado

Mais procurados

RxJS Evolved
RxJS EvolvedRxJS Evolved
RxJS Evolvedtrxcllnt
 
rx.js make async programming simpler
rx.js make async programming simplerrx.js make async programming simpler
rx.js make async programming simplerAlexander Mostovenko
 
Luis Atencio on RxJS
Luis Atencio on RxJSLuis Atencio on RxJS
Luis Atencio on RxJSLuis Atencio
 
Letswift19-clean-architecture
Letswift19-clean-architectureLetswift19-clean-architecture
Letswift19-clean-architectureJung Kim
 
Functional Reactive Programming / Compositional Event Systems
Functional Reactive Programming / Compositional Event SystemsFunctional Reactive Programming / Compositional Event Systems
Functional Reactive Programming / Compositional Event SystemsLeonardo Borges
 
Think Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJSThink Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJSAdam L Barrett
 
Concurrency Concepts in Java
Concurrency Concepts in JavaConcurrency Concepts in Java
Concurrency Concepts in JavaDoug Hawkins
 
RxJS - The Reactive extensions for JavaScript
RxJS - The Reactive extensions for JavaScriptRxJS - The Reactive extensions for JavaScript
RxJS - The Reactive extensions for JavaScriptViliam Elischer
 
Writing native bindings to node.js in C++
Writing native bindings to node.js in C++Writing native bindings to node.js in C++
Writing native bindings to node.js in C++nsm.nikhil
 
Letswift18 워크숍#1 스위프트 클린코드와 코드리뷰
Letswift18 워크숍#1 스위프트 클린코드와 코드리뷰Letswift18 워크숍#1 스위프트 클린코드와 코드리뷰
Letswift18 워크숍#1 스위프트 클린코드와 코드리뷰Jung Kim
 
Rainer Grimm, “Functional Programming in C++11”
Rainer Grimm, “Functional Programming in C++11”Rainer Grimm, “Functional Programming in C++11”
Rainer Grimm, “Functional Programming in C++11”Platonov Sergey
 
[Let'Swift 2019] 실용적인 함수형 프로그래밍 워크샵
[Let'Swift 2019] 실용적인 함수형 프로그래밍 워크샵[Let'Swift 2019] 실용적인 함수형 프로그래밍 워크샵
[Let'Swift 2019] 실용적인 함수형 프로그래밍 워크샵Wanbok Choi
 
JavaScript 2016 for C# Developers
JavaScript 2016 for C# DevelopersJavaScript 2016 for C# Developers
JavaScript 2016 for C# DevelopersRick Beerendonk
 

Mais procurados (20)

RxJS Evolved
RxJS EvolvedRxJS Evolved
RxJS Evolved
 
rx.js make async programming simpler
rx.js make async programming simplerrx.js make async programming simpler
rx.js make async programming simpler
 
Luis Atencio on RxJS
Luis Atencio on RxJSLuis Atencio on RxJS
Luis Atencio on RxJS
 
Letswift19-clean-architecture
Letswift19-clean-architectureLetswift19-clean-architecture
Letswift19-clean-architecture
 
Functional Reactive Programming / Compositional Event Systems
Functional Reactive Programming / Compositional Event SystemsFunctional Reactive Programming / Compositional Event Systems
Functional Reactive Programming / Compositional Event Systems
 
Angular2 rxjs
Angular2 rxjsAngular2 rxjs
Angular2 rxjs
 
Think Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJSThink Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJS
 
V8
V8V8
V8
 
Concurrency Concepts in Java
Concurrency Concepts in JavaConcurrency Concepts in Java
Concurrency Concepts in Java
 
ES6 in Real Life
ES6 in Real LifeES6 in Real Life
ES6 in Real Life
 
Map kit light
Map kit lightMap kit light
Map kit light
 
RxJS - The Reactive extensions for JavaScript
RxJS - The Reactive extensions for JavaScriptRxJS - The Reactive extensions for JavaScript
RxJS - The Reactive extensions for JavaScript
 
D3.js workshop
D3.js workshopD3.js workshop
D3.js workshop
 
Writing native bindings to node.js in C++
Writing native bindings to node.js in C++Writing native bindings to node.js in C++
Writing native bindings to node.js in C++
 
Letswift18 워크숍#1 스위프트 클린코드와 코드리뷰
Letswift18 워크숍#1 스위프트 클린코드와 코드리뷰Letswift18 워크숍#1 스위프트 클린코드와 코드리뷰
Letswift18 워크숍#1 스위프트 클린코드와 코드리뷰
 
Rainer Grimm, “Functional Programming in C++11”
Rainer Grimm, “Functional Programming in C++11”Rainer Grimm, “Functional Programming in C++11”
Rainer Grimm, “Functional Programming in C++11”
 
[Let'Swift 2019] 실용적인 함수형 프로그래밍 워크샵
[Let'Swift 2019] 실용적인 함수형 프로그래밍 워크샵[Let'Swift 2019] 실용적인 함수형 프로그래밍 워크샵
[Let'Swift 2019] 실용적인 함수형 프로그래밍 워크샵
 
The State of JavaScript
The State of JavaScriptThe State of JavaScript
The State of JavaScript
 
Oop assignment 02
Oop assignment 02Oop assignment 02
Oop assignment 02
 
JavaScript 2016 for C# Developers
JavaScript 2016 for C# DevelopersJavaScript 2016 for C# Developers
JavaScript 2016 for C# Developers
 

Destaque

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

Destaque (10)

혁신적인 웹컴포넌트 라이브러리 - Polymer
혁신적인 웹컴포넌트 라이브러리 - Polymer혁신적인 웹컴포넌트 라이브러리 - Polymer
혁신적인 웹컴포넌트 라이브러리 - Polymer
 
Angular2 ecosystem
Angular2 ecosystemAngular2 ecosystem
Angular2 ecosystem
 
System webpack-jspm
System webpack-jspmSystem webpack-jspm
System webpack-jspm
 
Functional Reactive Programming With RxSwift
Functional Reactive Programming With RxSwiftFunctional Reactive Programming With RxSwift
Functional Reactive Programming With RxSwift
 
[1B4]안드로이드 동시성_프로그래밍
[1B4]안드로이드 동시성_프로그래밍[1B4]안드로이드 동시성_프로그래밍
[1B4]안드로이드 동시성_프로그래밍
 
NDC14 - Rx와 Functional Reactive Programming으로 고성능 서버 만들기
NDC14 - Rx와 Functional Reactive Programming으로 고성능 서버 만들기NDC14 - Rx와 Functional Reactive Programming으로 고성능 서버 만들기
NDC14 - Rx와 Functional Reactive Programming으로 고성능 서버 만들기
 
RxJS and Reactive Programming - Modern Web UI - May 2015
RxJS and Reactive Programming - Modern Web UI - May 2015RxJS and Reactive Programming - Modern Web UI - May 2015
RxJS and Reactive Programming - Modern Web UI - May 2015
 
Module, AMD, RequireJS
Module, AMD, RequireJSModule, AMD, RequireJS
Module, AMD, RequireJS
 
Ionic으로 모바일앱 만들기 #1
Ionic으로 모바일앱 만들기 #1Ionic으로 모바일앱 만들기 #1
Ionic으로 모바일앱 만들기 #1
 
[124] 하이브리드 앱 개발기 김한솔
[124] 하이브리드 앱 개발기 김한솔[124] 하이브리드 앱 개발기 김한솔
[124] 하이브리드 앱 개발기 김한솔
 

Semelhante a Compose Async with RxJS

RESTful API using scalaz (3)
RESTful API using scalaz (3)RESTful API using scalaz (3)
RESTful API using scalaz (3)Yeshwanth Kumar
 
User Defined Aggregation in Apache Spark: A Love Story
User Defined Aggregation in Apache Spark: A Love StoryUser Defined Aggregation in Apache Spark: A Love Story
User Defined Aggregation in Apache Spark: A Love StoryDatabricks
 
User Defined Aggregation in Apache Spark: A Love Story
User Defined Aggregation in Apache Spark: A Love StoryUser Defined Aggregation in Apache Spark: A Love Story
User Defined Aggregation in Apache Spark: A Love StoryDatabricks
 
JS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless BebopJS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless BebopJSFestUA
 
Apache Spark for Library Developers with William Benton and Erik Erlandson
 Apache Spark for Library Developers with William Benton and Erik Erlandson Apache Spark for Library Developers with William Benton and Erik Erlandson
Apache Spark for Library Developers with William Benton and Erik ErlandsonDatabricks
 
Refactoring to Macros with Clojure
Refactoring to Macros with ClojureRefactoring to Macros with Clojure
Refactoring to Macros with ClojureDmitry Buzdin
 
CodiLime Tech Talk - Katarzyna Ziomek-Zdanowicz: RxJS main concepts and real ...
CodiLime Tech Talk - Katarzyna Ziomek-Zdanowicz: RxJS main concepts and real ...CodiLime Tech Talk - Katarzyna Ziomek-Zdanowicz: RxJS main concepts and real ...
CodiLime Tech Talk - Katarzyna Ziomek-Zdanowicz: RxJS main concepts and real ...CodiLime
 
Apache Spark in your likeness - low and high level customization
Apache Spark in your likeness - low and high level customizationApache Spark in your likeness - low and high level customization
Apache Spark in your likeness - low and high level customizationBartosz Konieczny
 
Expert JavaScript tricks of the masters
Expert JavaScript  tricks of the mastersExpert JavaScript  tricks of the masters
Expert JavaScript tricks of the mastersAra Pehlivanian
 
Monitoring Your ISP Using InfluxDB Cloud and Raspberry Pi
Monitoring Your ISP Using InfluxDB Cloud and Raspberry PiMonitoring Your ISP Using InfluxDB Cloud and Raspberry Pi
Monitoring Your ISP Using InfluxDB Cloud and Raspberry PiInfluxData
 
code for quiz in my sql
code for quiz  in my sql code for quiz  in my sql
code for quiz in my sql JOYITAKUNDU1
 
Async Redux Actions With RxJS - React Rally 2016
Async Redux Actions With RxJS - React Rally 2016Async Redux Actions With RxJS - React Rally 2016
Async Redux Actions With RxJS - React Rally 2016Ben Lesh
 
MongoDB World 2019: Life In Stitch-es
MongoDB World 2019: Life In Stitch-esMongoDB World 2019: Life In Stitch-es
MongoDB World 2019: Life In Stitch-esMongoDB
 
RxJava applied [JavaDay Kyiv 2016]
RxJava applied [JavaDay Kyiv 2016]RxJava applied [JavaDay Kyiv 2016]
RxJava applied [JavaDay Kyiv 2016]Igor Lozynskyi
 
Functional UIs with Java 8 and Vaadin JavaOne2014
Functional UIs with Java 8 and Vaadin JavaOne2014Functional UIs with Java 8 and Vaadin JavaOne2014
Functional UIs with Java 8 and Vaadin JavaOne2014hezamu
 
Job Queue in Golang
Job Queue in GolangJob Queue in Golang
Job Queue in GolangBo-Yi Wu
 

Semelhante a Compose Async with RxJS (20)

RESTful API using scalaz (3)
RESTful API using scalaz (3)RESTful API using scalaz (3)
RESTful API using scalaz (3)
 
User Defined Aggregation in Apache Spark: A Love Story
User Defined Aggregation in Apache Spark: A Love StoryUser Defined Aggregation in Apache Spark: A Love Story
User Defined Aggregation in Apache Spark: A Love Story
 
User Defined Aggregation in Apache Spark: A Love Story
User Defined Aggregation in Apache Spark: A Love StoryUser Defined Aggregation in Apache Spark: A Love Story
User Defined Aggregation in Apache Spark: A Love Story
 
JS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless BebopJS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless Bebop
 
Apache Spark for Library Developers with William Benton and Erik Erlandson
 Apache Spark for Library Developers with William Benton and Erik Erlandson Apache Spark for Library Developers with William Benton and Erik Erlandson
Apache Spark for Library Developers with William Benton and Erik Erlandson
 
Refactoring to Macros with Clojure
Refactoring to Macros with ClojureRefactoring to Macros with Clojure
Refactoring to Macros with Clojure
 
CodiLime Tech Talk - Katarzyna Ziomek-Zdanowicz: RxJS main concepts and real ...
CodiLime Tech Talk - Katarzyna Ziomek-Zdanowicz: RxJS main concepts and real ...CodiLime Tech Talk - Katarzyna Ziomek-Zdanowicz: RxJS main concepts and real ...
CodiLime Tech Talk - Katarzyna Ziomek-Zdanowicz: RxJS main concepts and real ...
 
Apache Spark in your likeness - low and high level customization
Apache Spark in your likeness - low and high level customizationApache Spark in your likeness - low and high level customization
Apache Spark in your likeness - low and high level customization
 
Expert JavaScript tricks of the masters
Expert JavaScript  tricks of the mastersExpert JavaScript  tricks of the masters
Expert JavaScript tricks of the masters
 
Fact, Fiction, and FP
Fact, Fiction, and FPFact, Fiction, and FP
Fact, Fiction, and FP
 
Monitoring Your ISP Using InfluxDB Cloud and Raspberry Pi
Monitoring Your ISP Using InfluxDB Cloud and Raspberry PiMonitoring Your ISP Using InfluxDB Cloud and Raspberry Pi
Monitoring Your ISP Using InfluxDB Cloud and Raspberry Pi
 
Rxjs swetugg
Rxjs swetuggRxjs swetugg
Rxjs swetugg
 
Rxjs marble-testing
Rxjs marble-testingRxjs marble-testing
Rxjs marble-testing
 
code for quiz in my sql
code for quiz  in my sql code for quiz  in my sql
code for quiz in my sql
 
Async Redux Actions With RxJS - React Rally 2016
Async Redux Actions With RxJS - React Rally 2016Async Redux Actions With RxJS - React Rally 2016
Async Redux Actions With RxJS - React Rally 2016
 
Rx java in action
Rx java in actionRx java in action
Rx java in action
 
MongoDB World 2019: Life In Stitch-es
MongoDB World 2019: Life In Stitch-esMongoDB World 2019: Life In Stitch-es
MongoDB World 2019: Life In Stitch-es
 
RxJava applied [JavaDay Kyiv 2016]
RxJava applied [JavaDay Kyiv 2016]RxJava applied [JavaDay Kyiv 2016]
RxJava applied [JavaDay Kyiv 2016]
 
Functional UIs with Java 8 and Vaadin JavaOne2014
Functional UIs with Java 8 and Vaadin JavaOne2014Functional UIs with Java 8 and Vaadin JavaOne2014
Functional UIs with Java 8 and Vaadin JavaOne2014
 
Job Queue in Golang
Job Queue in GolangJob Queue in Golang
Job Queue in Golang
 

Último

Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsArshad QA
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providermohitmore19
 
Clustering techniques data mining book ....
Clustering techniques data mining book ....Clustering techniques data mining book ....
Clustering techniques data mining book ....ShaimaaMohamedGalal
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...kellynguyen01
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfkalichargn70th171
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...panagenda
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Modelsaagamshah0812
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...ICS
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software DevelopersVinodh Ram
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionSolGuruz
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxComplianceQuest1
 
Hand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxHand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxbodapatigopi8531
 
Test Automation Strategy for Frontend and Backend
Test Automation Strategy for Frontend and BackendTest Automation Strategy for Frontend and Backend
Test Automation Strategy for Frontend and BackendArshad QA
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVshikhaohhpro
 
Salesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantSalesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantAxelRicardoTrocheRiq
 

Último (20)

Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
 
Clustering techniques data mining book ....
Clustering techniques data mining book ....Clustering techniques data mining book ....
Clustering techniques data mining book ....
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
 
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Models
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
 
Microsoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdfMicrosoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdf
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software Developers
 
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS LiveVip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
 
Exploring iOS App Development: Simplifying the Process
Exploring iOS App Development: Simplifying the ProcessExploring iOS App Development: Simplifying the Process
Exploring iOS App Development: Simplifying the Process
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with Precision
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docx
 
Hand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxHand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptx
 
Test Automation Strategy for Frontend and Backend
Test Automation Strategy for Frontend and BackendTest Automation Strategy for Frontend and Backend
Test Automation Strategy for Frontend and Backend
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 
Salesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantSalesforce Certified Field Service Consultant
Salesforce Certified Field Service Consultant
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
 

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