JavaScript 中的非同步程式設計(回呼、Promise、RxJs)

大家好。 謝爾蓋·奧梅爾尼茨基已與您聯繫。 不久前,我主持了一個關於反應式程式設計的直播,其中我討論了 JavaScript 中的非同步。 今天我想對這個資料做筆記。

JavaScript 中的非同步程式設計(回呼、Promise、RxJs)

但在開始主要材料之前,我們需要先做一個介紹說明。 那麼讓我們先從定義開始:什麼是堆疊和佇列?

疊放 是一個集合,其元素是按照後進先出 LIFO 原則取得的

是一個集合,其元素是按照先進先出 FIFO 原則取得的

好吧,我們繼續。

JavaScript 中的非同步程式設計(回呼、Promise、RxJs)

JavaScript 是一種單執行緒程式語言。 這意味著只有一個執行緒和一個堆疊,函數在堆疊上排隊等待執行。 因此,JavaScript 一次只能執行一個操作,而其他操作將在堆疊上等待,直到被呼叫。

呼叫堆疊 是一種資料結構,簡單來說,記錄了我們在程式中所處位置的資訊。 如果我們傳入一個函數,我們會將其條目推入堆疊頂部。 當我們從函數返回時,我們從堆疊中彈出最頂層的元素,並最終回到我們呼叫函數的位置。 這就是堆疊所能做的全部事情。 現在是一個非常有趣的問題。 那麼非同步在 JavaScript 中是如何運作的呢?

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('Hi') 從呼叫堆疊中刪除。

JavaScript 中的非同步程式設計(回呼、Promise、RxJs)

5) 現在繼續執行指令 setTimeout(function cb1() {… })。 它被添加到呼叫堆疊中。

JavaScript 中的非同步程式設計(回呼、Promise、RxJs)

6) 執行setTimeout(function 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 中的非同步程式設計只能透過函數來實現。 它們可以像任何其他變數一樣傳遞給其他函數。 這就是回調的誕生方式。 而且很酷、很好玩、很好玩,直到變成悲傷、惆悵、悲傷。 為什麼? 這很簡單:

  • 隨著程式碼複雜性的增加,專案很快就會變成晦澀的、重複嵌套的區塊——「回調地獄」。
  • 錯誤處理很容易被忽略。
  • 不能用 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 來解決嵌套非同步問題

但承諾也有其限制。 例如,如果不敲著手鼓跳舞,就無法取消承諾,最重要的是它符合一個值。

好吧,我們已經順利地接近了響應式程式設計。 疲勞的? 好吧,幸運的是,你可以去泡杯茶,想一想,然後再回來閱讀更多內容。 我會繼續。

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)

接收JS 是一個使用可觀察序列處理非同步和基於事件的程式的庫。 該庫提供了基本類型 可觀察,幾種輔助類型(觀察者、調度者、受試者) 和用於像處理集合一樣處理事件的運算子 (映射、過濾、減少、每個 以及 JavaScript 陣列中的類似內容)。

讓我們來了解這個庫的基本概念。

可觀察者、觀察者、生產者

Observable 是我們要討論的第一個基本類型。 此類包含 RxJs 實作的主要部分。 它與一個可觀察流關聯,可以使用 subscribe 方法訂閱該流。

Observable 實作了一種用於創建更新的輔助機制,即所謂的 觀察員。 觀察者的值的來源稱為 製片人。 這可以是陣列、迭代器、Web 套接字、某種事件等。 所以我們可以說observable是Producer和Observer之間的導體。

Observable 處理三種類型的觀察者事件:

  • 下一步 – 新數據
  • error – 如果序列因異常而結束,則會出現錯誤。 此事件也意味著序列的完成。
  • 完成——關於序列完成的訊號。 這意味著不會有更多新數據。

讓我們看一下示範:

JavaScript 中的非同步程式設計(回呼、Promise、RxJs)

一開始我們將處理數值1、2、3以及1秒後。 我們將得到 4 並結束我們的流。

把想法大聲說出來

然後我意識到講述它比寫它更有趣。 😀

平台會員計畫

當我們訂閱流時,我們會建立一個新類 訂閱這使我們能夠使用該方法取消訂閱 退訂。 我們也可以使用該方法對訂閱進行分組 添加。 好吧,我們可以使用以下命令取消線程分組,這是合乎邏輯的 清除。 新增和刪除方法接受另一個訂閱作為輸入。 我想指出的是,當我們取消訂閱時,我們會取消所有子訂閱的訂閱,就像它們呼叫了取消訂閱方法一樣。 前進。

流的類型


Producer 是在 observable 外部創建的
生產者是在 observable 內部創建的

資料在建立可觀察物件時傳輸
數據在訂閱時提供

需要額外的邏輯來取消訂閱
執行緒自行終止

使用一對多關係
使用一對一關係

所有訂閱都具有相同的含義
訂閱是獨立的

如果您沒有訂閱,資料可能會遺失
重新發出新訂閱的所有串流值

打個比方,我會把熱流想像成電影院裡的電影。 你在什麼時間到達,從那一刻起你就開始觀看。 我將冷流與技術中的呼叫進行比較。 支持。 任何來電者都會從頭到尾收聽語音郵件錄音,但您可以使用取消訂閱掛斷電話。

我想指出的是,還有所謂的暖流(我很少遇到這個定義,僅在國外社區) - 這是一種從冷流轉變為熱流的流。 問題出現了-在哪裡使用))我將給出一個實踐中的例子。

我正在使用 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)
    );

操作符使我們能夠擴展我們使用流的能力。 它們有助於控制 Observable 中發生的事件。 我們將研究幾個最受歡迎的操作符,並且可以使用有用資訊中的連結找到有關操作符的更多詳細資訊。

運算符 - of

讓我們從輔助運算子 of 開始。 它基於一個簡單的值創建一個 Observable。

JavaScript 中的非同步程式設計(回呼、Promise、RxJs)

運算子 - 過濾器

JavaScript 中的非同步程式設計(回呼、Promise、RxJs)

過濾器運算符,顧名思義,過濾流訊號。 如果運算子傳回 true,則進一步跳過。

運營商-採取

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)

運營商-combineLatest

mergeLatest 運算子有點類似promise.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)

電信商 – 分享、點擊

Tap 運算子可讓您執行副作用,即任何不影響序列的操作。

共享公用事業營運商可以將冷流變成熱流。

JavaScript 中的非同步程式設計(回呼、Promise、RxJs)

我們與營運商的關係已經結束了。 讓我們繼續主題。

把想法大聲說出來

然後我就去喝茶了。 我厭倦了這些例子😀

對象家庭

這個主題族是熱流的一個典型例子。 這些類別是一種混合體,同時充當可觀察者和觀察者。 由於主題是熱門話題,因此需要取消訂閱。 如果我們談論主要方法,那麼這些是:

  • next – 將新資料傳輸到串流
  • error – 錯誤和執行緒終止
  • 完成——線程的完成
  • 訂閱 – 訂閱串流
  • 取消訂閱 – 取消訂閱串流
  • asObservable – 轉變為觀察者
  • toPromise——轉化為承諾

有4 5種科目。

把想法大聲說出來

直播裡本來有4個人在說話,結果又加了一個。 正如他們所說,生活和學習。

簡單主題 new Subject()– 最簡單的科目類型。 創建時不帶參數。 僅傳輸訂閱後收到的值。

行為主體 new BehaviorSubject( defaultData<T> ) – 在我看來,這是最常見的主題類型。 輸入採用預設值。 始終保存訂閱時傳輸的最新一期資料。 該類別還有一個有用的 value 方法,它會傳回流的當前值。

重播主題 new ReplaySubject(bufferSize?: number, windowTime?: number) — 輸入可以選擇將本身儲存的值緩衝區的大小作為第一個參數,將我們需要更改的時間作為第二個參數。

非同步主題 new AsyncSubject() — 訂閱時不會發生任何事情,只有完成後才會回傳值。 僅返回流的最後一個值。

WebSocket主題 new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) — 文檔中沒有提及他,而我還是第一次見到他。 如果有人知道他做了什麼,請寫下來,我們會添加它。

唷。 好吧,我們今天已經涵蓋了我想告訴你的所有內容。 我希望這些資訊有用。 您可以在有用資訊標籤中自行閱讀參考文獻清單。

有用的信息

來源: www.habr.com

添加評論