1. 程式人生 > 其它 >題解Promise併發控制

題解Promise併發控制

https://juejin.cn/post/6916317088521027598
https://juejin.cn/post/6916317088521027598

Promise

1. 理解

Promise是非同步程式設計的一種解決方案。
Promise 是一個建構函式,接收一個函式作為引數,返回一個 Promise 例項
Promise的例項有三個狀態:

  • Pending(進行中)
  • Resolved(已完成)
  • Rejected(已拒絕)
    Promise的例項有兩個過程:
  • pending -> fulfilled : Resolved(已完成)
  • pending -> rejected:Rejected(已拒絕)

注意:一旦從進行狀態變成為其他狀態就永遠不能更改狀態了。
注意:在構造 Promise 的時候,建構函式內部的程式碼是立即執行的。

2.方法

Promise有五個常用的方法:then()、catch()、all()、race()、finally。

  • then()
    then方法可以接受兩個回撥函式作為引數。第一個回撥函式是Promise物件的狀態變為resolved時呼叫,第二個回撥函式是Promise物件的狀態變為rejected時呼叫。其中第二個引數可以省略。
    then方法返回的是一個新的Promise例項(不是原來那個Promise例項)。因此可以採用鏈式寫法,即then方法後面再呼叫另一個then方法。
  • catch()
    該方法相當於then方法的第二個引數,指向reject的回撥函式。不過catch方法還有一個作用,就是在執行resolve回撥函式時,如果出現錯誤,丟擲異常,不會停止執行,而是進入catch方法中。
  • all()
    all方法可以完成並行任務, 它接收一個數組,陣列的每一項都是一個promise物件。當陣列中所有的promise的狀態都達到resolved的時候,all方法的狀態就會變成resolved,如果有一個狀態變成了rejected,那麼all方法的狀態就會變成rejected。
  • race()
    race方法和all一樣,接受的引數是一個每項都是promise的陣列,但是與all不同的是,當最先執行完的事件執行完之後,就直接返回該promise物件的值。如果第一個promise物件狀態變成resolved,那自身的狀態變成了resolved;反之第一個promise變成rejected,那自身狀態就會變成rejected。
  • finally()
    finally方法用於指定不管 Promise 物件最後狀態如何,都會執行的操作。該方法是 ES2018 引入標準的。

3. async/await

async是“非同步”的簡寫,await則為等待,所以很好理解async 用於申明一個 function 是非同步的,而 await 用於等待一個非同步方法執行完成。

異常捕獲

async function fn(){
    try{
        let a = await Promise.reject('error')
    }catch(error){
        console.log(error)
    }
}

async/await對比Promise的優勢

  • 程式碼讀起來更加同步,Promise雖然擺脫了回撥地獄,但是then的鏈式調⽤也會帶來額外的閱讀負擔
  • Promise傳遞中間值⾮常麻煩,⽽async/await⼏乎是同步的寫法,⾮常優雅
  • 錯誤處理友好,async/await可以⽤成熟的try/catch,Promise的錯誤捕獲⾮常冗餘
  • 除錯友好,Promise的除錯很差,由於沒有程式碼塊,你不能在⼀個返回表示式的箭頭函式中設定斷點,如果你在⼀個.then程式碼塊中使⽤偵錯程式的步進(step-over)功能,偵錯程式並不會進⼊後續的.then程式碼塊,因為偵錯程式只能跟蹤同步程式碼的每⼀步。

4. 題目

實現一個批量請求函式 multiRequest(urls, maxNum),要求如下:
• 要求最大併發數 maxNum
• 每當有一個請求返回,就留下一個空位,可以增加新的請求
• 所有請求完成後,結果按照 urls 裡面的順序依次打出

5. 解答

// 整體採用遞迴呼叫來實現:最初發送的請求數量上限為允許的最大值,並且這些請求中的每一個都應該在完成時繼續遞迴傳送,通過傳入的索引來確定了urls裡面具體是那個URL,保證最後輸出的順序不會亂,而是依次輸出。
function multiRequest(urls = [], maxNum) {
  // 請求總數量
  const len = urls.length;
  // 根據請求數量建立一個數組來儲存請求的結果
  const result = new Array(len).fill(false);
  // 當前完成的數量
  let count = 0;

  return new Promise((resolve, reject) => {
    // 請求maxNum個
    while (count < maxNum) {
      next();
    }
    function next() {
      let current = count++;
      // 處理邊界條件
      if (current >= len) {
        // 請求全部完成就將promise置為成功狀態, 然後將result作為promise值返回
        !result.includes(false) && resolve(result);
        return;
      }
      const url = urls[current];
      console.log(`開始 ${current}`, new Date().toLocaleString());
      fetch(url)
        .then((res) => {
          // 儲存請求結果
          result[current] = res;
          console.log(`完成 ${current}`, new Date().toLocaleString());
          // 請求沒有全部完成, 就遞迴
          if (current < len) {
            next();
          }
        })
        .catch((err) => {
          console.log(`結束 ${current}`, new Date().toLocaleString());
          result[current] = err;
          // 請求沒有全部完成, 就遞迴
          if (current < len) {
            next();
          }
        });
    }
  });
}