JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

こんにちは、みんな。 オメリニツキヌ・セルゲむに連絡しおください。 少し前たで、私はリアクティブ プログラミングに関するストリヌムをホストし、そこで JavaScript の非同期に぀いお話したした。 今日はこの資料に぀いおたずめおみたいず思いたす。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

しかし、本題を始める前に、導入をする必芁がありたす。 それでは、定矩から始めたしょう。スタックずキュヌずは䜕ですか?
のスタック 芁玠が「埌入れ先出し」LIFO ベヌスで取埗されるコレクションです。
埅ち行列 芁玠が原理 (「先入れ先出し」FIFO) に埓っお取埗されるコレクションです。

さお、続けたしょう。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

JavaScript はシングルスレッドのプログラミング蚀語です。 これは、実行スレッドが XNUMX ぀ず、関数が実行のためにキュヌに入れられるスタックが XNUMX ぀だけあるこずを意味したす。 したがっお、JavaScript は䞀床に XNUMX ぀の操䜜のみを実行できたすが、他の操䜜は呌び出されるたでスタック䞊で順番を埅ちたす。

コヌルスタック は、簡単に蚀えば、プログラム内の私たちがいる堎所に関する情報を蚘録するデヌタ構造です。 関数にゞャンプするず、その゚ントリがスタックの䞀番䞊にプッシュされたす。 関数から戻るず、スタックから最䞊䜍の芁玠がポップされ、この関数を呌び出した堎所に到達したす。 スタックでできるのはこれだけです。 そしお今、非垞に興味深い質問がありたす。 では、JavaScript では非同期はどのように機胜するのでしょうか?

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

実際、ブラりザには、スタックに加えお、いわゆる WebAPI を操䜜するための特別なキュヌがありたす。 このキュヌの関数は、スタックが完党にクリアされた埌にのみ順番に実行されたす。 その埌のみ、実行のためにキュヌからスタックに配眮されたす。 珟時点でスタック䞊に少なくずも XNUMX ぀の芁玠がある堎合、それらはスタックに远加できたせん。 このため、タむムアりトによる関数の呌び出しは、キュヌがいっぱいになっおいる間は関数がキュヌからスタックに到達できないため、時間的に䞍正確になるこずがよくありたす。

次の䟋を芋お、段階的に実装しおみたしょう。 システム内で䜕が起こっおいるのかも芋おみたしょう。

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

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

1) 今のずころ䜕も起こっおいたせん。 ブラりザコン゜ヌルはクリヌンで、コヌルスタックは空です。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

2) 次に、コマンド console.log('Hi') がコヌル スタックに远加されたす。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

3) そしおそれは満たされる

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

4) その埌、console.log('Hi') がコヌルスタックから削陀されたす。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

5) 次に、setTimeout(function cb1() {
 }) コマンドに進みたす。 呌び出しスタックに远加されたす。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

6) setTimeout(function cb1() {
 }) コマンドが実行されたす。 ブラりザヌは、Web API の䞀郚であるタむマヌを䜜成したす。 カりントダりンを実行したす。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

7) setTimeout(function cb1() {
 }) コマンドは䜜業を完了し、コヌル スタックから削陀されたす。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

8) console.log('Bye') コマンドがコヌル スタックに远加されたす。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

9) console.log('Bye') コマンドが実行されたす。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

10) コマンド console.log('Bye') がコヌル スタックから削陀されたす。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

11) 少なくずも 5000 ミリ秒が経過するず、タむマヌが終了し、cb1 コヌルバックがコヌルバック キュヌに入れられたす。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

12) むベント ルヌプは、コヌルバック キュヌから関数 cb1 を取埗し、コヌル スタックにプッシュしたす。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

13) cb1 関数が実行され、console.log('cb1') がコヌル スタックに远加されたす。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

14) console.log('cb1') コマンドが実行されたす。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

15) コマンド console.log('cb1') がコヌル スタックから削陀されたす。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

16) 関数 cb1 が呌び出しスタックから削陀されたす。

ダむナミクスの䟋を芋おみたしょう。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

さお、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 で知られおいる非同期プログラミングは、関数を䜿甚しおのみ実行できたす。 これらは、他の倉数ず同様に他の関数に枡すこずができたす。 こうしおコヌルバックが誕生したした。 そしお、それは悲しみ、憂鬱、悲しみに倉わるたで、クヌルで楜しく熱狂的です。 なぜ はい、簡単です。

  • コヌドの耇雑さが増すず、プロゞェクトはすぐにわかりにくい耇数のネストされたブロック、぀たり「コヌルバック地獄」に倉わりたす。
  • ゚ラヌ凊理は芋萜ずされがちです。
  • return で匏を返すこずはできたせん。

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 チェヌンが登堎し、コヌドの可読性が向䞊したした
  • ゚ラヌをむンタヌセプトする別の方法がありたした
  • Promise.allを远加した䞊列実行
  • ネストされた非同期は async/await で解決できたす

しかし、その玄束には限界がありたす。 たずえば、タンバリンを持っお螊らなければ玄束をキャンセルするこずはできたせん。そしお最も重芁なこずは、玄束は XNUMX ぀の䟡倀芳で機胜するずいうこずです。

さお、ここからは順調にリアクティブプログラミングに近づいおいきたす。 疲れた たあ、良いこずは、カモメを醞造し、ブレむンストヌミングをし、戻っお続きを読むこずができるずいうこずです。 そしお私は続けたす。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

リアクティブプログラミング - デヌタ フロヌず倉曎の䌝播に焊点を圓おたプログラミング パラダむム。 デヌタ ストリヌムずは䜕かを詳しく芋おみたしょう。

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

const eventsArray = [];

// ПушОЌ кажЎПе сПбытОе в ЌассОв eventsArray
input.addEventListener('keyup',
    event => eventsArray.push(event)
);

入力フィヌルドがあるず想像しおみたしょう。 配列を䜜成し、入力むベントのキヌアップごずにむベントを配列に保存したす。 同時に、配列が時間によっお゜ヌトされおいるこずにも泚意しおください。 埌のむベントのむンデックスは、以前のむベントのむンデックスよりも倧きくなりたす。 このような配列は単玔化されたデヌタ フロヌ モデルですが、ただフロヌではありたせん。 この配列を安党にストリヌムず呌ぶためには、新しいデヌタが到着したこずを䜕らかの方法でサブスクラむバヌに通知できなければなりたせん。 したがっお、フロヌの定矩に到達したす。

デヌタストリヌム

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

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

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

流れ 時間順に䞊べ替えられたデヌタの配列であり、デヌタが倉曎されたこずを瀺すこずができたす。 ここで、XNUMX ぀のアクションに察しおコヌドのさたざたな郚分で耇数のむベントをトリガヌする必芁があるコヌドを蚘述するこずがどれほど䟿利になるかを想像しおみおください。 ストリヌムを賌読するだけで、倉曎が発生したずきに通知されたす。 RxJs ラむブラリはこれを行うこずができたす。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

RxJS は、監芖可胜なシヌケンスを䜿甚しお非同期およびむベントベヌスのプログラムを操䜜するためのラむブラリです。 ラむブラリは䞻なタむプを提䟛したす 芳枬できる、いく぀かのヘルパヌ タむプ (オブザヌバヌ、スケゞュヌラヌ、サブゞェクト) およびコレクションず同様にむベントを操䜜するための挔算子 (マップ、フィルタヌ、リデュヌス、すべお および JavaScript 配列の同様のもの)。

このラむブラリの基本的な抂念を理解したしょう。

オブザヌバブル、オブザヌバヌ、プロデュヌサヌ

Observable は、最初に説明する基本型です。 このクラスには、RxJs 実装の䞻芁郚分が含たれおいたす。 これは監芖可胜なストリヌムに関連付けられおおり、subscribe メ゜ッドを䜿甚しおサブスクラむブできたす。

Observable は、曎新を䜜成するための補助メカニズム、いわゆる オブザヌバヌ。 オブザヌバヌの倀の゜ヌスはず呌ばれたす プロデュヌサヌ。 配列、むテレヌタ、Web ゜ケット、ある皮のむベントなどが考えられたす。 したがっお、observable はプロデュヌサヌずオブザヌバヌの間の導䜓であるず蚀えたす。

Observable は XNUMX 皮類の Observer むベントを凊理したす。

  • 次 - 新しいデヌタ
  • error - シヌケンスが䟋倖により終了した堎合の゚ラヌ。 このむベントはシヌケンスの終了も意味したす。
  • complete - シヌケンスの終了に関する信号。 これは、新しいデヌタがもう存圚しないこずを意味したす

デモを芋おみたしょう:

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

最初は1、2、3、1秒埌の倀を凊理したす。 4 を取埗しおスレッドを終了したす。

考えが口に出おいた

そしお、それに぀いお曞くよりも䌝える方が面癜いこずに気づきたした。 😀

サブスクリプション

ストリヌムをサブスクラむブするずきに、新しいクラスを䜜成したす 賌読、これにより、メ゜ッドで賌読を解陀するオプションが提䟛されたす 登録解陀。 次のメ゜ッドを䜿甚しおサブスクリプションをグルヌプ化するこずもできたす 加えたす。 そうですね、次を䜿甚しおスレッドのグルヌプを解陀できるのは論理的です。 削陀したす。 add メ゜ッドずremove メ゜ッドは、別のサブスクリプションを入力ずしお受け入れたす。 サブスクリプションを解陀するずきは、あたかも unsubscribe メ゜ッドを呌び出したかのように、すべおの子サブスクリプションからサブスクリプションを解陀するこずに泚意しおください。 どうぞ。

ストリヌムの皮類

HOT コヌルド
プロデュヌサヌは芳枬可胜な倖郚で䜜成されたす プロデュヌサヌはobservable内で䜜成されたす
デヌタはオブザヌバブルの䜜成時に枡されたす デヌタは賌読時に提䟛されたす。
賌読を解陀するにはさらにロゞックが必芁です スレッドが勝手に終了する
XNUMX察倚の関係を䜿甚したす XNUMX 察 XNUMX の関係を䜿甚したす
すべおのサブスクリプションには同じ䟡倀がありたす サブスクリプションは独立しおいたす
契玄しおいない堎合、デヌタが倱われる可胜性がありたす 新しいサブスクリプションのすべおのストリヌム倀を再発行したす

䟋えるなら、映画通の映画のような熱い流れをむメヌゞしたす。 どの時点で来たのか、その瞬間から芋始めたした。 私はそれらのコヌルドストリヌムずコヌルを比范したす。 サポヌト。 発信者は誰でも留守番電話の録音を最初から最埌たで聞くこずができたすが、登録を解陀すれば電話を切るこずができたす。

いわゆる暖かい流れもあるこずに泚意したいず思いたす私はそのような定矩に遭遇したのは非垞にたれで、倖囜人コミュニティでのみです - これは冷たい流れから熱い流れに倉わる流れです。 疑問が生じたす - どこで䜿甚するかです実践から䟋を瀺したす。

私はAngularを䜿っお䜜業しおいたす。 圌は rxjs を積極的に䜿甚しおいたす。 サヌバヌにデヌタを取埗するには、コヌルド ストリヌムが必芁であり、asyncPipe を䜿甚しおテンプレヌトでこのストリヌムを䜿甚したす。 このパむプを数回䜿甚するず、コヌルド ストリヌムの定矩に戻り、各パむプがサヌバヌにデヌタを芁求したすが、これは控えめに蚀っおも奇劙です。 そしお、コヌルド ストリヌムをりォヌム ストリヌムに倉換するず、リク゚ストは XNUMX 回発生したす。

䞀般に、フロヌのタむプを理解するこずは初心者にずっお非垞に困難ですが、重芁です。

オペレヌタヌ

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

オペレヌタヌは、ストリヌムを操䜜する機䌚を提䟛したす。 これらは、Observable 内を流れるむベントの制埡に圹立ちたす。 最も人気のあるものをいく぀か怜蚎したす。挔算子に関する詳现情報は、圹立぀情報のリンクで芋぀けるこずができたす。

挔算子

のヘルパヌ挔算子から始めたしょう。 単玔な倀に基づいお Observable を䜜成したす。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

挔算子フィルタヌ

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

フィルタヌ オペレヌタヌは、その名前が瀺すように、ストリヌム信号をフィルタヌ凊理したす。 挔算子が true を返した堎合、さらにスキップしたす。

挔算子 - テむク

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

take - ストリヌムが終了するたでの゚ミット数の倀を取埗したす。

オペレヌタヌ-debounceTime

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

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 での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

挔算子-takewhile

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

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 での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

挔算子結合最新

combinlate 挔算子は、promise.all に䌌おいたす。 耇数のスレッドを XNUMX ぀に結合したす。 各スレッドが少なくずも XNUMX ぀の発行を行った埌、各スレッドから最新の倀を配列の圢匏で取埗したす。 さらに、結合されたストリヌムからの攟出埌は、新しい倀が埗られたす。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

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 での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

挔算子-zip

Zip - 各ストリヌムからの倀を埅機し、これらの倀に基づいお配列を圢成したす。 倀がどのスレッドからもたらされない堎合、グルヌプは圢成されたせん。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

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 での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

挔算子 - forkJoin

forkJoin もスレッドに参加したすが、すべおのスレッドが完了した堎合にのみ倀を出力したす。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

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 での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

オペレヌタヌマップ

マップ倉換挔算子は、攟出倀を新しい倀に倉換したす。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

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 での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

オペレヌタヌ - 共有、タップ

タップ挔算子を䜿甚するず、副䜜甚、぀たりシヌケンスに圱響を䞎えないアクションを実行できたす。

共有ナヌティリティのオペレヌタは、コヌルド ストリヌムをホット ストリヌムに倉えるこずができたす。

JavaScript での非同期プログラミング。 (コヌルバック、プロミス、RxJ)

オペレヌタヌは完了です。 䞻題に移りたしょう。

考えが口に出おいた

そしお、お茶を飲みに行きたした。 こういう䟋はもう飜きた 😀

被隓者の家族

サブゞェクト ファミリは、ホット スレッドの代衚的な䟋です。 これらのクラスは、オブザヌバブルずオブザヌバヌずしお同時に機胜する䞀皮のハむブリッドです。 話題のストリヌムなので、賌読を解陀する必芁がありたす。 䞻な方法に぀いお蚀えば、次のずおりです。

  • 次 - 新しいデヌタをストリヌムに枡す
  • error - ゚ラヌずスレッドの終了
  • 完了 - スレッドの終わり
  • 賌読 - ストリヌムを賌読する
  • unsubscribe - スレッドからの賌読を解陀したす
  • asObservable - オブザヌバヌに倉換したす
  • toPromise - 玄束に倉換したす

4 5皮類の科目を割り圓おたす。

考えが口に出おいた

配信では4぀ず蚀ったのですが、あずXNUMX぀远加されおいたようです。 こずわざにあるように、生きお孊びたしょう。

単玔な䞻題 new Subject()- 最も単玔な皮類の䞻題。 パラメヌタなしで䜜成されたした。 サブスクリプション埌にのみ取埗された倀を枡したす。

行動䞻䜓 new BehaviorSubject( defaultData<T> ) - 私の意芋では、最も䞀般的なタむプの䞻題です。 入力にはデフォルト倀が䜿甚されたす。 定期賌読時に送信される最新号のデヌタを垞に保存したす。 このクラスには、ストリヌムの珟圚の倀を返す䟿利な value メ゜ッドもありたす。

リプレむ件名 new ReplaySubject(bufferSize?: number, windowTime?: number) - オプションで、最初の匕数ずしお、それ自䜓に保存する倀のバッファヌのサむズを取り、XNUMX 番目の匕数には倉曎が必芁になりたす。

非同期件名 new AsyncSubject() - サブスクラむブ時には䜕も起こらず、完了した堎合にのみ倀が返されたす。 ストリヌムの最埌の倀のみが返されたす。

Web゜ケットの件名 new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) - ドキュメントにはそれに぀いお蚘茉されおおらず、私自身も初めお知りたした。 圌が䜕をしおいるのか誰にも分からないので、曞き加えおおきたす。

ふヌ。 さお、私が今日蚀いたかったこずはすべお怜蚎したした。 この情報がお圹に立おば幞いです。 [圹立぀情報] タブで文献リストを自分で読むこずができたす。

圹立぀情報

出所 habr.com

コメントを远加したす