Евгений Поздняков
Приложение
Память
до: 3.5гб
после: 600мб
Время загрузки
до: 1мин
после: 20сек
Рендеринг
до: 30-40сек
после: 1сек
Скорость ответа
до: 10сек
после: <1сек
| 1 | args | |
|---|---|---|
| sync | value | array |
| async | promise | stream/pipe |
Async vs Sync
Просто messaging и pub-sub?
Модель Rx
Producer
Consumer
Data pipeline
Time
Declarative
Immutable
Pure, side effect -free
Map, reduce, iterator
Functional Programming
Promise
Observable
Массивы как источник
source - массив или объект
from - создает Observable-последовательность из array-like или iterable объекта
of - создает Observable-последовательность из переданных аргументов
const task_stream =
// Стрим из всех данных в базе
getTasks().
// выбираем задания только для текущего пользователя
filter((task) => task.user_id == user_id).
// находим те, которые еще не завершились
filter((task) => !task.completed).
// берем только название
map((task) => task.name)Observable.from(source)
.operatorOne()
.operatorTwo()
.oneMoreOperator()
.aaanddMoreOperator()
.filter(value => value)
.subscribe(console.log);События как источник
Typeahead
return Observable.fromEvent(this.elemnt.nativeElement, 'keyup')
.map((e:any) => e.target.value)
.debounceTime(450)
.concat()
.distinctUntilChanged()
.subscribe(item => this.keywordChange.emit(item));А происходит следующее
closed = false
Утечка памяти!
return Observable.fromEvent(this.elemnt.nativeElement, 'keyup')
.map((e:any) => e.target.value)
.debounceTime(450)
.concat()
.distinctUntilChanged()
.subscribe(item => this.keywordChange.emit(item));private subscription: Subscription;
onInit(){
this.subscription = this.listenAndSuggest();
}
listenAndSuggest(){
return Observable
.fromEvent(this.elemnt.nativeElement, 'keyup')
.map((e:any) => e.target.value)
.debounceTime(450)
.concat()
.distinctUntilChanged()
.subscribe(item => this.keywordChange.emit(item));
}
Мы забыли отписаться от Cold Pipe
Мы забыли отписаться от Cold Pipe
В коде выше некому сделать Unsubscribe
Как решить проблему?
interface Observer<T> {
closed?: boolean;
next: (value: T) => void;
error: (err: any) => void;
complete: () => void;
}
unsubscribe(): void {
let hasErrors = false;
let errors: any[];
if (this.closed) {
return;
}
let { _parent, _ parents, _unsubscribe, _subscriptions } = (<any> this);
this.closed = true;
this._parent = null;
this._parents = null;
this._subscriptions = null;
}Мы забыли отписаться от Cold Pipe
Стрим нужен пока жива вся компонента, давайте отпишемся, когда компота будет больше не нужна
А что делать, если мы используем много Observable? Руками отписываться?
private subscription: Subscription;
onInit(){
this.subscription = this.listenAndSuggest();
}
listenAndSuggest(){
return Observable
.fromEvent(this.elemnt.nativeElement, 'keyup')
.map((e:any) => e.target.value)
.debounceTime(450)
.concat()
.distinctUntilChanged()
.subscribe(item => this.keywordChange.emit(item));
}
onDestroy(){
this.subscription.unsubscribe();
}Google советует использовать следующие методы:
Потому что:
take ()
export function take<T> count: number): MonoTypeOperatorFunction<T> {
return (source: Observable<T>) => {
if (count === 0) {
return empty();
} else {
return source.lift(new TakeOperator(count));
}
};
}private nextOrComplete(value: T, predicateResult: boolean): void {
const destination = this.destination;
if (Boolean(predicateResult)) {
destination.next(value);
} else {
destination.complete();
}
}take ()
Все хорошо
Утечка памяти
Observable.from([1,2,3])
.filter(value => false)
.take(0)
.subscribe(console.log)Observable.from([1,2,3])
.filter(value => false)
.take(1)
.subscribe(console.log)takeWhile ()
protected _next(value: T): void {
const destination = this.destination;
let result: boolean;
try {
result = this.predicate(value, this.index++);
} catch (err) {
destination.error(err);
return;
}
this.nextOrComplete(value, result);
}
private nextOrComplete(value: T, predicateResult: boolean): void {
const destination = this.destination;
if (Boolean(predicateResult)) {
destination.next(value);
} else {
destination.complete();
}
}takeWhile ()
в случае, если через pipe ничего не пролетит, после того, как isDestroyed станет true, подписка никогда не закроется
takeWhile закроет подписку только если предикат isDestroyed= true в момент, когда через pipe пролетит значение
isDestroyed = false;
listenAndSuggest(){
return Observable
.fromEvent(this.element.nativeElement, 'keyup')
.takeWhile(() => this.isDestroyed)
.subscribe(console.log);
}
onDestroy(){
this.isDestroyed = true;
}Утечка памяти
first()
Одно но
protected _complete(): void {
const destination = this.destination;
if (!this.hasCompleted && typeof this.defaultValue !== 'undefined') {
destination.next(this.defaultValue);
destination.complete();
} else if (!this.hasCompleted) {
destination.error(new EmptyError);
}
}If called with no arguments, 'first' emits the first value of the sourse Observable, then completes. If called with a 'predicate' function, 'first' emits the first value of the source that matches the specified condition.
takeUntil ()
Все честно, главное сделать emit
notifyNext(outerValue: T, innerValue: R,
outerIndex: number, innerIndex: number,
innerSub: InnerSubscriber<T, R>): void {
this.complete();
}const timerOne = Rx.Observable.timer(1000, 4000);
const timerTwo = Rx.Observable.timer(2000, 4000)
const timerThree = Rx.Observable.timer(3000, 4000)
const combined = Rx.Observable
.timer(2, 1000)
.takeUntil(this.componentDestroy)
.merge(
timerOne,
timerTwo,
timerThree,
)
.subscribe(console.log) --> 0 0 1 2 3 4 1 5 6 7 8 2 9 10 11 12 3 13 14 15 16Утечка памяти
merge создает новый Observable, takeUntil закроет только все, что до merge
const timerOne = Rx.Observable.timer(1000, 4000);
const timerTwo = Rx.Observable.timer(2000, 4000)
const timerThree = Rx.Observable.timer(3000, 4000)
const combined = Rx.Observable
.timer(2, 1000)
.merge(
timerOne,
timerTwo,
timerThree,
)
.takeUntil(this.componentDestroy)
.subscribe(console.log) --> 0 0 1 2 3 4 1 5 6 7 8 2 9 10 11 12 3 13 14 15 16Все хорошо
merge закроет все подписки
Слишком много лишнего кода, еще некрасивые присваивания
export function unsubscribeManager( blackList = [] ) {
return function ( constructor ) {
const originalOnDestroy = constructor.prototype.ngOnDestroy;
constructor.prototype.ngOnDestroy = function () {
for ( let prop in this ) {
const property = this[ prop ];
if ( !blackList.includes(prop) ) {
if ( property && ( typeof property.unsubscribe === "function" ) ) {
property.unsubscribe();
}
}
}
original &&
typeof originalOnDestroy === 'function' &&
originalOnDestroy(this, arguments);
};
}
}import { Subject } from 'rxjs/Subject';
export function TakeUntilDestroy(constructor: any) {
const originalNgOnDestroy = constructor.prototype.ngOnDestroy;
constructor.prototype.componentDestroy = function () {
this._takeUntilDestroy$ = this._takeUntilDestroy$ || new Subject();
return this._takeUntilDestroy$.asObservable();
};
constructor.prototype.ngOnDestroy = function () {
if (this._takeUntilDestroy$) {
this._takeUntilDestroy$.next(true);
this._takeUntilDestroy$.complete();
}
if (originalNgOnDestroy && typeof originalNgOnDestroy === 'function') {
originalNgOnDestroy.apply(this, arguments);
}
};
}
18к
Angular Router Reuse Strategy
id: 1
'/ route / 1'
'/ route / 2'
id: 1
'/ route / 1'
'/ route / 2'
id: 2
Ищем место, где Angular добавляет все компоненты в дерево
Собираем мусор, чистим консоль. Запускаем сборщик мусора.