Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

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

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

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

Стак е збирка чии елементи се добиени на основа на LIFO-последен влез, прв излез

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

Добро, да продолжиме.

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

JavaScript е програмски јазик со една нишка. Ова значи дека има само една нишка на извршување и еден стек на кој функциите се редат за извршување. Затоа, JavaScript може да изврши само една операција во исто време, додека другите операции ќе го чекаат својот ред на оџакот додека не се повикаат.

стек за повикување е структура на податоци која, едноставно кажано, снима информации за местото во програмата каде што се наоѓаме. Ако преминеме во функција, го туркаме нејзиниот влез до врвот на стекот. Кога се враќаме од некоја функција, го исфрламе најгорниот елемент од оџакот и завршуваме таму каде што ја повикавме функцијата. Ова е сè што може да направи оџакот. И сега едно исклучително интересно прашање. Како тогаш функционира асинхронијата во JavasScript?

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

Всушност, покрај стекот, прелистувачите имаат посебна редица за работа со таканаречениот WebAPI. Функциите во оваа редица ќе се извршуваат по редослед само откако стекот ќе биде целосно исчистен. Само после ова тие се туркаат од редот на оџакот за извршување. Ако во моментот има барем еден елемент на оџакот, тогаш тие не можат да се додадат во оџакот. Токму поради ова повикувањето функции со истекување често не е прецизно во времето, бидејќи функцијата не може да стигне од редот до оџакот додека е полн.

Ајде да го погледнеме следниот пример и да започнеме со неговата чекор-по-чекор имплементација. Ајде да видиме и што се случува во системот.

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

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

1) Сè уште ништо не се случува. Конзолата на прелистувачот е чиста, оџакот за повици е празен.

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

2) Потоа командата console.log('Hi') се додава во купот за повици.

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

3) И се исполнува

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

4) Потоа console.log ('Здраво') се отстранува од купот на повици.

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

5) Сега преминете на командата setTimeout(функција cb1() {… }). Се додава во купот на повици.

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

6) Се извршува командата setTimeout(функција cb1() {… }). Прелистувачот создава тајмер што е дел од Web API. Ќе изврши одбројување.

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

7) Командата setTimeout(function cb1() {... }) ја заврши својата работа и е отстранета од купот повици.

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

8) Командата console.log('Bye') се додава во купот за повици.

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

9) Се извршува командата console.log('Bye').

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

10) Командата console.log('Bye') е отстранета од оџакот за повици.

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

11) Откако ќе поминат најмалку 5000 ms, тајмерот завршува и го става повратниот повик cb1 во редот за повратен повик.

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

12) Јамката за настани ја зема функцијата cb1 од редот за повратен повик и ја става на купот за повици.

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

13) Функцијата cb1 се извршува и ја додава console.log('cb1') во стекот на повици.

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

14) Се извршува командата console.log('cb1').

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

15) Командата console.log('cb1') е отстранета од купот на повици.

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

16) Функцијата cb1 е отстранета од купот на повици.

Ајде да погледнеме пример во динамиката:

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

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

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

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);
                    })
                })
            })
        })
    })
});

Асинхроното програмирање како што го знаеме во JavaScript може да се имплементира само со функции. Тие може да се пренесат како и секоја друга променлива на други функции. Така се родиле повратните повици. И тоа е кул, забавно и разиграно, додека не се претвори во тага, меланхолија и тага. Зошто? Едноставно е:

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

Со доаѓањето на Promise, ситуацијата стана малку подобра.

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);
});

  • Се појавија синџири за ветување, кои ја подобрија читливоста на кодот
  • Се појави посебен метод за фаќање грешки
  • Додадена е можност за паралелно извршување користејќи Promise.all
  • Можеме да ја решиме вгнездената асинхронија користејќи async/wait

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

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

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

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

// Получаем ссылку на элемент
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)
)

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

Проток е низа податоци подредени по време што може да укаже дека податоците се промениле. Сега замислете колку е погодно да се пишува код во кој едно дејство бара повикување на неколку настани во различни делови од кодот. Едноставно се претплатиме на преносот и тој ќе не извести кога ќе се случат промени. И библиотеката RxJs може да го направи ова.

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

RxJS е библиотека за работа со асинхрони и програми засновани на настани со користење на набљудувачки секвенци. Библиотеката обезбедува основен тип Набудувачки, неколку помошни типови (Набљудувач, Распоредувачи, Субјекти) и оператори за работа со настани како со колекции (карта, филтрирање, намалување, секој и слични од JavaScript Array).

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

Забележливо, Набљудувач, Продуцент

Забележливо е првиот основен тип што ќе го разгледаме. Оваа класа го содржи главниот дел од имплементацијата на RxJs. Поврзан е со набљудувачки поток, на кој може да се претплатите со користење на методот на претплата.

Observable имплементира помошен механизам за креирање ажурирања, т.н Набљудувач. Се нарекува изворот на вредности за набљудувачот Производителот. Ова може да биде низа, итератор, веб-сокет, некој вид на настан итн. Значи, можеме да кажеме дека забележливо е диригент помеѓу Продуцентот и Набљудувачот.

Observable се справува со три типа на настани на Observer:

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

Ајде да го видиме демото:

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

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

Размислувајќи гласно

И тогаш сфатив дека кажувањето е поинтересно отколку пишувањето за тоа. 😀

Претплата

Кога се претплатиме на пренос, создаваме нова класа претплаташто ни дава можност да се откажеме со користење на методот отпишување. Можеме да групираме претплати користејќи го методот додадете. Па, логично е да можеме да ги одгрупираме нишките користејќи отстрани. Методите за додавање и отстранување прифаќаат друга претплата како влез. Би сакал да забележам дека кога се отпишуваме, се отпишуваме од сите детски претплати како да го повикале методот на отпишување. Само напред.

Видови потоци

HOT
СОЦД

Производителот е создаден надвор што може да се набљудува
Производителот е создаден внатре забележлив

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

Потребна е дополнителна логика за отпишување
Темата завршува сама по себе

Користи врска еден-на-многу
Користи врска еден на еден

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

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

Да дадам аналогија, јас би помислил на жежок стрим како филм во театар. Во кој момент пристигнавте, од тој момент почнавте да гледате. Би го споредил студениот тек со повик во технологија. поддршка. Секој повикувач ја слуша снимката на говорната пошта од почеток до крај, но можете да ја прекинете слушалката со отпишување.

Би сакал да забележам дека има и таканаречени топли текови (на оваа дефиниција сум сретнал исклучително ретко и само во странски заедници) - ова е проток што се трансформира од студен тек во топол. Се поставува прашањето - каде да се користи)) Ќе дадам пример од практиката.

Работам со Angular. Тој активно користи rxjs. За да примам податоци на серверот, очекувам ладна нишка и ја користам оваа нишка во шаблонот користејќи asyncPipe. Ако ја користам оваа цевка неколку пати, тогаш, враќајќи се на дефиницијата за ладен поток, секоја цевка ќе бара податоци од серверот, што е во најмала рака чудно. И ако конвертирам ладен поток во топол, тогаш барањето ќе се случи еднаш.

Во принцип, разбирањето на типот на текови е доста тешко за почетници, но важно.

Оператори

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

Операторите ни обезбедуваат можност да ја прошириме нашата способност за работа со стримови. Тие помагаат да се контролираат настаните што се случуваат во набљудуваното. Ќе разгледаме неколку од најпопуларните, а повеќе детали за операторите може да се најдат користејќи ги врските во корисните информации.

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

Да почнеме со помошниот оператор на. Создава набљудување врз основа на едноставна вредност.

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

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

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

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

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

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

take — Ја зема вредноста на бројот на емитери, по што конецот завршува.

Оператори - debounceTime

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

debounceTime - ги отфрла емитираните вредности кои спаѓаат во наведениот временски интервал помеѓу излезите - откако ќе помине временскиот интервал, ја емитува последната вредност.

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)
);  

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

Оператори - takeWhile

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

Емитира вредности додека takeWhile не врати false, по што се отпишува од нишката.

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 )
);  

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

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

Операторот combinationLatest е нешто сличен на ветувањето.all. Комбинира повеќе нишки во една. Откако секоја нишка ќе направи барем една емисија, ги добиваме најновите вредности од секоја во форма на низа. Понатаму, по секоја емисија од споените текови, тоа ќе даде нови вредности.

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

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));

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

Оператори - zip

Zip - чека вредност од секоја нишка и формира низа врз основа на овие вредности. Ако вредноста не доаѓа од ниту една нишка, тогаш групата нема да се формира.

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

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));

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

Оператори - forkJoin

forkJoin, исто така, ги спојува нишките, но емитува вредност само кога сите нишки се завршени.

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

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);

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

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

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

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

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)
);

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

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

Операторот на чешма ви овозможува да правите несакани ефекти, односно какви било дејства што не влијаат на низата.

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

Асинхроно програмирање во JavaScript (Повратен повик, Promise, RxJs)

Завршивме со операторите. Ајде да продолжиме на Тема.

Размислувајќи гласно

И тогаш отидов да пијам чај. Доста ми е од овие примери 😀

Предметно семејство

Предметното семејство е одличен пример за топли текови. Овие класи се еден вид хибрид кои истовремено дејствуваат како набљудувани и набљудувачи. Бидејќи темата е жешка нишка, неопходно е да се откажете од неа. Ако зборуваме за главните методи, тогаш ова се:

  • следно – пренос на нови податоци на потокот
  • грешка – грешка и завршување на конецот
  • комплетен – завршување на конецот
  • претплатете се - претплатете се на пренос
  • отпишете - отпишете се од преносот
  • asObservable – се трансформира во набљудувач
  • toPromise – се претвора во ветување

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

Размислувајќи гласно

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

Едноставен предмет new Subject()– наједноставниот тип на предмети. Создаден без параметри. Ги пренесува вредностите добиени само по претплата.

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

ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) — Влезот може опционално да ја земе како прв аргумент големината на баферот на вредности што ќе ги складира сам по себе, а како втор времето во кое ни се потребни промени.

AsyncSubject new AsyncSubject() — ништо не се случува при претплатата, а вредноста ќе се врати само кога ќе заврши. Ќе се врати само последната вредност на преносот.

WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) - Документацијата молчи за него и првпат се гледам со него. Ако некој знае што прави нека пише и ние ќе го додадеме.

Пју. Па, опфативме сè што сакав да ви кажам денес. Се надевам дека оваа информација беше корисна. Можете сами да го прочитате списокот со референци во табот корисни информации.

Корисни информации

Извор: www.habr.com

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