什麼是Promise串列

  • 2020 年 2 月 25 日
  • 筆記

什麼是Promise串列

Promise串列是指每一個由promise封裝的任務都順序執行,即上一個執行完成後再執行下一個。

執行過程大致是下面的樣子:

Task A | ------>|  Task B |         ------>|  Task C |                 ------>|  Task D |                         ------>|

為什麼要講Promise串列

我們平時會比較多的使用並行,即多個任務一起執行,也就是利用Promise.all()。但其實在日常開發中串列也是會遇到的,比如依次開啟走廊的所有燈,或者其次讓噴泉的水槍依次噴水等等。但是ES6中的Promise並沒有對串列進行直接封裝,所以需要我們自己來做。

img

分布講解Promise串列

Promise串列習題

之前有小夥伴發給過我一道這樣的面試題,所以本文準備通過這道題來實現一下Promise串列。

定義 type Task = () => Promise (即 Task 是一個 類型,是一個返回值是 Promise 的函數類型) 假設有一個數組 tasks: Task[](每一項都是一個 Task 類型的數組) 實現一個方法 function execute(tasks: Task[]): Promise,該方法將 tasks 內的任務 依次 執行,並返回一個結果為數組的 Promise ,該數組包含任務執行結果(以執行順序排序) 要求:忽略異常任務,並在結果數組中用 null 佔位 限制:不添加任何依賴,僅使用 Promise,不使用 Generator 或 async

如果允許使用Generator或者async/await來寫的話,會很簡單,文章末尾再實現async/await的方法。

先做完成一下測試用例的程式碼:

const Task = (result, isSuccess = true) => {    return () => new Promise((resolve, reject) => {      setTimeout(() => {        if (isSuccess) {          console.log(`success: ${result}`);          resolve(result);        } else {          console.log(`error: ${result}`);          reject(result);        }      }, 1000);    });  }      execute([    Task('A'),    Task('B'),    Task('X', false),    Task('C'),  ]).then(resultList => {    // 這裡期望列印 ["A", "B", null, "C"]    console.log(resultList)  })

思路大致如下圖:先做一個Promise實例,然後把每個Task循環的放置到上一個promisethen回調里。

需要注意的幾點:

  1. 無論每個Task是成功還是失敗,它都不能阻斷下一個Task的執行
  2. 最後的then需要把每個Task的執行結果"決議"出去

對策:

  1. 每一個Task外層包裝一層Promise,捕獲Task的reject狀態
  2. 可以利用一個中間變數,快取所有Task的輸出結果,然後在最後一個Promise的then里把中間變數「決議」出去

第一版程式碼如下:

function execute(tasks) {      let resultList = [];    return tasks.reduce(      (previousPromise, currentPromise) => previousPromise.then((resultList) => {      return new Promise(resolve => {        currentPromise().then(result => {                  resultList.push(result);          resolve()        }).catch(() => {                    resultList.push(null);                resolve(resultList.concat(null))        })      })    }),      Promise.resolve()    ).then(() => resultList);  }

改進

其實Promise的鏈式操作是可以傳遞值的,所以可以利用這個特性,省去中間變數,

程式碼如下:

function execute(tasks) {    return tasks.reduce(      (previousPromise, currentPromise) => previousPromise.then((resultList) => {      return new Promise(resolve => {        currentPromise().then(result => {          resolve(resultList.concat(result))        }).catch(() => {          resolve(resultList.concat(null))        })      })    }),      Promise.resolve([])    )  }

aysnc/await版本

程式碼如下:

const execute = async (tasks = []) => {    const resultList = [];    for(task of tasks) {      try {        resultList.push(await task());      } catch (e) {        resultList.push(null);      }    }    return resultList;  }