Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

Здраво свима. Сергеи Омелнитски је у контакту. Недавно сам водио стрим о реактивном програмирању, где сам говорио о асинхронији у ЈаваСцрипт-у. Данас бих желео да направим белешке о овом материјалу.

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

Али пре него што почнемо са главним материјалом, треба да направимо уводну белешку. Дакле, почнимо са дефиницијама: шта је стек и ред?

Одрезак је колекција чији се елементи добијају по принципу ЛИФО последњи ушао, први изашао

Ред чекања је колекција чији се елементи добијају на ФИФО принципу први ушао, први изашао

Ок, наставимо.

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

ЈаваСцрипт је једнонитни програмски језик. То значи да постоји само једна нит извршења и један стек на којем су функције стављене у ред за извршење. Стога, ЈаваСцрипт може да изврши само једну операцију истовремено, док ће друге операције чекати свој ред на стеку док се не позову.

Група позива је структура података која, једноставно речено, бележи информације о месту у програму где се налазимо. Ако пређемо у функцију, гурамо њен унос на врх стека. Када се вратимо из функције, избацујемо највиши елемент из стека и завршавамо тамо где смо позвали функцију. Ово је све што стек може. А сада изузетно занимљиво питање. Како онда асинхронија функционише у ЈавасСцрипт-у?

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

У ствари, поред стека, претраживачи имају посебан ред за рад са такозваним ВебАПИ. Функције у овом реду ће се извршавати по редоследу тек након што је стек потпуно очишћен. Тек након тога се гурају из реда у стек ради извршавања. Ако тренутно постоји бар један елемент на стеку, он се не може додати у стек. Управо због тога позивање функција по временском ограничењу често није прецизно, јер функција не може да дође из реда у стек док је пуна.

Погледајмо следећи пример и почнимо са његовом имплементацијом корак по корак. Да видимо и шта се дешава у систему.

console.log('Hi');
setTimeout(function cb1() {
    console.log('cb1');
}, 5000);
console.log('Bye');

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

1) Још се ништа не дешава. Конзола претраживача је чиста, стек позива је празан.

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

2) Затим се команда цонсоле.лог('Хи') додаје стеку позива.

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

3) И испуњено је

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

4) Затим се цонсоле.лог('Здраво') уклања из стека позива.

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

5) Сада пређите на команду сетТимеоут(фунцтион цб1() {… }). Додаје се у стек позива.

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

6) Извршава се команда сетТимеоут(фунцтион цб1() {… }). Прегледач креира тајмер који је део веб АПИ-ја. Извршиће одбројавање.

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

7) Команда сетТимеоут(фунцтион цб1() {... }) је завршила свој рад и уклоњена је из стека позива.

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

8) Команда цонсоле.лог('Бие') је додата стеку позива.

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

9) Извршава се команда цонсоле.лог('Бие').

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

10) Команда цонсоле.лог('Бие') је уклоњена из стека позива.

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

11) Након што је прошло најмање 5000 мс, тајмер се прекида и поставља повратни позив цб1 у ред за повратни позив.

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

12) Петља догађаја преузима функцију цб1 из реда повратног позива и поставља је у стек позива.

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

13) Функција цб1 се извршава и додаје цонсоле.лог('цб1') стеку позива.

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

14) Извршава се команда цонсоле.лог('цб1').

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

15) Команда цонсоле.лог('цб1') је уклоњена из стека позива.

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

16) Функција цб1 је уклоњена из стека позива.

Погледајмо пример у динамици:

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

Па, погледали смо како је асинхронија имплементирана у ЈаваСцрипт-у. Хајде да сада укратко говоримо о еволуцији асинхроног кода.

Еволуција асинхроног кода.

a(function (resultsFromA) {
    b(resultsFromA, function (resultsFromB) {
        c(resultsFromB, function (resultsFromC) {
            d(resultsFromC, function (resultsFromD) {
                e(resultsFromD, function (resultsFromE) {
                    f(resultsFromE, function (resultsFromF) {
                        console.log(resultsFromF);
                    })
                })
            })
        })
    })
});

Асинхроно програмирање какво познајемо у ЈаваСцрипт-у може се имплементирати само помоћу функција. Могу се прослеђивати као и свака друга променљива другим функцијама. Тако су рођени повратни позиви. И то је кул, забавно и разиграно, док се не претвори у тугу, меланхолију и тугу. Зашто? То је једноставно:

  • Како се комплексност кода повећава, пројекат се брзо претвара у нејасне, више пута угнежђене блокове - „пакао повратног позива“.
  • Руковање грешкама се може лако пропустити.
  • Не можете вратити изразе са ретурн.

Са појавом Промиса ситуација је постала мало боља.

new Promise(function(resolve, reject) {
    setTimeout(() => resolve(1), 2000);

}).then((result) => {
    alert(result);
    return result + 2;

}).then((result) => {
    throw new Error('FAILED HERE');
    alert(result);
    return result + 2;

}).then((result) => {
    alert(result);
    return result + 2;

}).catch((e) => {
    console.log('error: ', e);
});

  • Појавили су се ланци обећања, који су побољшали читљивост кода
  • Појавио се посебан метод за хватање грешака
  • Додата могућност паралелног извршавања помоћу Промисе.алл
  • Можемо да решимо угнежђену асинхронију користећи асинц/аваит

Али обећања имају своја ограничења. На пример, обећање се не може отказати без плеса уз тамбуру, а оно што је најважније је да ради са једном вредношћу.

Па, глатко смо приступили реактивном програмирању. Уморан? Па, на срећу, можете отићи да скувате чај, размислите о томе и вратите се да прочитате више. И наставићу.

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

Реактивно програмирање је програмска парадигма фокусирана на токове података и ширење промена. Хајде да ближе погледамо шта је ток података.

// Получаем ссылку на элемент
const input = ducument.querySelector('input');

const eventsArray = [];

// Пушим каждое событие в массив eventsArray
input.addEventListener('keyup',
    event => eventsArray.push(event)
);

Замислимо да имамо поље за унос. Креирамо низ и за сваку тастатуру улазног догађаја чуваћемо догађај у нашем низу. Истовремено, желим да напоменем да је наш низ поређан по времену, тј. индекс каснијих догађаја је већи од индекса ранијих. Такав низ је поједностављени модел тока података, али још увек није ток. Да би се овај низ безбедно могао назвати стримом, мора на неки начин да обавести претплатнике да су у њега стигли нови подаци. Тако долазимо до дефиниције тока.

Ток података

const { interval } = Rx;
const { take } = RxOperators;

interval(1000).pipe(
    take(4)
)

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

Флов је низ података сортираних по времену који може указати на то да су се подаци променили. Сада замислите колико је згодно писати код у коме једна радња захтева позивање неколико догађаја у различитим деловима кода. Једноставно се претплатимо на стрим и он ће нас обавестити када дође до промена. И РкЈс библиотека то може.

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

РкЈС је библиотека за рад са асинхроним програмима и програмима заснованим на догађајима који користе секвенце које се могу посматрати. Библиотека пружа основни тип Приметан, неколико помоћних типова (Посматрач, Планери, Субјекти) и оператори за рад са догађајима као са колекцијама (мапа, филтер, редукција, сваки и сличне из ЈаваСцрипт низа).

Хајде да разумемо основне концепте ове библиотеке.

Посматрач, Посматрач, Продуцент

Опсервабле је први основни тип који ћемо погледати. Ова класа садржи главни део имплементације РкЈс. Повезан је са видљивим током, на који се може претплатити коришћењем методе претплате.

Обсервабле имплементира помоћни механизам за креирање ажурирања, тзв Посматрати. Извор вредности за Посматрача се зове Продуцент. Ово може бити низ, итератор, веб утичница, нека врста догађаја, итд. Дакле, можемо рећи да је посматрано проводник између Продуцента и Посматрача.

Обсервабле обрађује три типа Обсервер догађаја:

  • следећи – нови подаци
  • грешка – грешка ако је низ прекинут због изузетка. овај догађај подразумева и завршетак низа.
  • комплетан — сигнал о завршетку низа. То значи да више неће бити нових података.

Да видимо демо:

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

На почетку ћемо обрадити вредности 1, 2, 3 и после 1 секунде. добићемо 4 и завршити наш стреам.

Размишљање наглас

А онда сам схватио да је причати о томе занимљивије него писати о томе. 😀

Претплата

Када се претплатимо на стрим, креирамо нову класу претплаташто нам даје могућност да одјавимо претплату користећи метод одјавити. Такође можемо груписати претплате користећи метод додати. Па, логично је да можемо да разгрупишемо нити користећи уклонити. Методе додавања и уклањања прихватају другу претплату као улаз. Желео бих да напоменем да када откажемо претплату, поништавамо претплату на све подређене претплате као да су позвали метод за одјаву. Хајде.

Врсте токова

Хот
ЦОЛД

Произвођач је креиран изван видљивог
Произвођач је креиран унутар видљивог

Подаци се преносе у време креирања посматраног
Подаци се дају у тренутку претплате

Потребна је додатна логика за одјаву
Нит се сама завршава

Користи однос један-према-више
Користи однос један-на-један

Све претплате имају исто значење
Претплате су независне

Подаци се могу изгубити ако немате претплату
Поново издаје све вредности стрима за нову претплату

Да дам аналогију, мислио бих о врућем току као о филму у позоришту. У ком тренутку сте стигли, од тог тренутка сте почели да гледате. Упоредио бих хладни ток са позивом у технику. подршка. Сваки позивалац слуша снимак говорне поште од почетка до краја, али можете прекинути везу користећи опозив пријаве.

Желео бих да напоменем да постоје и такозвани топли токови (на ову дефиницију сам наилазио изузетно ретко и само у страним заједницама) - то је ток који се из хладног тока претвара у топли. Поставља се питање - где да се користи)) Даћу пример из праксе.

Радим са Ангуларом. Активно користи ркјс. Да бих примио податке на сервер, очекујем хладну нит и користим ову нит у шаблону користећи асинцПипе. Ако користим ову цев неколико пута, онда, враћајући се на дефиницију хладног тока, свака цев ће захтевати податке од сервера, што је у најмању руку чудно. А ако претворим хладан ток у топли, онда ће се захтев десити једном.

Генерално, разумевање врсте токова је прилично тешко за почетнике, али важно.

operatori

return this.http.get(`${environment.apiUrl}/${this.apiUrl}/trade_companies`)
    .pipe(
        tap(({ data }: TradeCompanyList) => this.companies$$.next(cloneDeep(data))),
        map(({ data }: TradeCompanyList) => data)
    );

Оператери нам пружају могућност да проширимо нашу способност рада са стримовима. Они помажу да се контролишу догађаји који се дешавају у Обсервабле-у. Погледаћемо неколико најпопуларнијих, а више детаља о оператерима можете пронаћи на линковима у корисним информацијама.

Оператери - од

Почнимо са помоћним оператором од. Креира Обсервабле на основу једноставне вредности.

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

Оператери - филтер

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

Оператор филтера, као што име каже, филтрира сигнал стрима. Ако оператор врати труе, прескаче даље.

Оператери - узми

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

таке — Узима вредност броја емитера, након чега се нит завршава.

Оператори - дебоунцеТиме

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

дебоунцеТиме - одбацује емитоване вредности које спадају у одређени временски интервал између излаза - након што временски интервал прође, емитује последњу вредност.

const { Observable } = Rx;
const { debounceTime, take } = RxOperators;

Observable.create((observer) => {
  let i = 1;
  observer.next(i++);
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next(i++)
  }, 1000);

 // Испускаем значение раз в 1500мс
  setInterval(() => {
    observer.next(i++)
  }, 1500);
}).pipe(
  debounceTime(700),  // Ожидаем 700мс значения прежде чем обработать
  take(3)
);  

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

Оператери - такеВхиле

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

Емитује вредности све док такеВхиле не врати фалсе, након чега се одјављује са нити.

const { Observable } = Rx;
const { debounceTime, takeWhile } = RxOperators;

Observable.create((observer) => {
  let i = 1;
  observer.next(i++);
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next(i++)
  }, 1000);
}).pipe(
  takeWhile( producer =>  producer < 5 )
);  

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

Оператери - комбиниНајновије

ЦомбинЛатест оператор је донекле сличан обећању.алл. Комбинује више нити у једну. Након што свака нит направи најмање једну емисију, добијамо најновије вредности од сваке у облику низа. Даље, након било какве емисије из спојених токова, то ће дати нове вредности.

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

const { combineLatest, Observable } = Rx;
const { take } = RxOperators;

const observer_1 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next('a: ' + i++);
  }, 1000);
});

const observer_2 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 750мс
  setInterval(() => {
    observer.next('b: ' + i++);
  }, 750);
});

combineLatest(observer_1, observer_2).pipe(take(5));

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

Оператери - зип

Зип – Чека вредност из сваке нити и формира низ на основу ових вредности. Ако вредност не долази ни из једне нити, онда се група неће формирати.

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

const { zip, Observable } = Rx;
const { take } = RxOperators;

const observer_1 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next('a: ' + i++);
  }, 1000);
});

const observer_2 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 750
  setInterval(() => {
    observer.next('b: ' + i++);
  }, 750);
});

const observer_3 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 500
  setInterval(() => {
    observer.next('c: ' + i++);
  }, 500);
});

zip(observer_1, observer_2, observer_3).pipe(take(5));

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

Оператори - форкЈоин

форкЈоин такође спаја нити, али емитује вредност само када су све нити завршене.

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

const { forkJoin, Observable } = Rx;
const { take } = RxOperators;

const observer_1 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next('a: ' + i++);
  }, 1000);
}).pipe(take(3));

const observer_2 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 750
  setInterval(() => {
    observer.next('b: ' + i++);
  }, 750);
}).pipe(take(5));

const observer_3 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 500
  setInterval(() => {
    observer.next('c: ' + i++);
  }, 500);
}).pipe(take(4));

forkJoin(observer_1, observer_2, observer_3);

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

Оператери - карта

Оператор трансформације мапе трансформише вредност емитера у нову.

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

const {  Observable } = Rx;
const { take, map } = RxOperators;

Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next(i++);
  }, 1000);
}).pipe(
  map(x => x * 10),
  take(3)
);

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

Оператери – поделите, додирните

Оператор тап вам омогућава да урадите нежељене ефекте, односно све радње које не утичу на секвенцу.

Оператер комуналних услуга може хладан ток претворити у врућ.

Асинхроно програмирање у ЈаваСцрипт-у (повратни позив, обећање, РкЈс)

Завршили смо са оператерима. Пређимо на предмет.

Размишљање наглас

А онда сам отишао да попијем чај. Уморан сам од ових примера 😀

Предметна породица

Предметна породица је одличан пример врућих токова. Ове класе су нека врста хибрида који делују истовремено као посматрач и посматрач. Пошто је тема врућа нит, потребно је да се одјавите са ње. Ако говоримо о главним методама, онда су то:

  • следеће – пренос нових података у ток
  • грешка – грешка и прекид нити
  • комплетан – завршетак нити
  • претплатити се – претплатити се на стрим
  • одјавити се – одјавити се са стрима
  • асОбсервабле – трансформисати се у посматрача
  • тоПромисе – претвара се у обећање

Постоји 4 врста предмета.

Размишљање наглас

У стриму су разговарале 4 особе, али се испоставило да су додали још једног. Како кажу, живи и учи.

Симпле Субјецт new Subject()– најједноставнији тип предмета. Креирано без параметара. Преноси вредности примљене тек након претплате.

БехавиорСубјецт new BehaviorSubject( defaultData<T> ) – по мом мишљењу, најчешћи тип предмета. Улаз узима подразумевану вредност. Увек чува податке последњег издања, који се преносе приликом претплате. Ова класа такође има метод корисне вредности, који враћа тренутну вредност тока.

РеплаиСубјецт new ReplaySubject(bufferSize?: number, windowTime?: number) — Улаз може опционо да узме као први аргумент величину бафера вредности које ће похранити у себе, а као други време током којег су нам потребне промене.

АсинцСубјецт new AsyncSubject() — ништа се не дешава када се претплатите, а вредност ће бити враћена тек када се заврши. Биће враћена само последња вредност тока.

ВебСоцкетСубјецт new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) — Документација о њему ћути и први пут га видим. Ако неко зна шта ради нека напише и ми ћемо то додати.

Фуј. Па, покрили смо све што сам желео да вам кажем данас. Надам се да су ове информације биле корисне. Листу референци можете сами прочитати на картици корисних информација.

корисне информације

Извор: ввв.хабр.цом

Додај коментар