1. 程式人生 > 其它 >方法 手寫promise_手寫原始碼系列二——Promise相關方法

方法 手寫promise_手寫原始碼系列二——Promise相關方法

技術標籤:方法 手寫promise

歡迎關注微信公眾號——“較真的前端”,裡面有更多前端面試題和解答。

手寫Promise相關方法

Promise是面試中經常遇到的,如果面試中面試官問你Promise.all()怎麼用,那你面試的崗位差不多是高階前端開發的崗位,但如果讓你手寫一個Promise.all()那你面試的崗位應該就是資深/專家前端開發的崗位了

上期回顧

上期我們實現了函式的call()、bind()、apply()方法。

Promise.all()

先回顧一下Promise.all()的用法

Promise.all(iterable) 方法返回一個 Promise 例項,此例項在 iterable

引數內所有的 promise 都“完成(resolved)”或引數中不包含 promise 時回撥完成(resolve);如果引數中 promise 有一個失敗(rejected),此例項回撥失敗(reject),失敗原因的是第一個失敗 promise 的結果。

例子如下:

var promise1 = Promise.resolve(3);
var promise2 = 42;
var promise3 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then(function(values) {
  console.log(values);
});
// expected output: Array [3, 42, "foo"]

手寫實現Promise.all()方法

直接上程式碼:

Promise.myAll = function (iterators) {
  const promises = Array.from(iterators)
  const len = promises.length
  let count = 0
  let resultList = []
  return new Promise((resolve, reject) => {
    promises.forEach((p, index) => {
      Promise.resolve(p)
        .then((result) => {
          count++
          resultList[index] = result
          if (count === len) {
            resolve(resultList)
          }
        })
        .catch(e => {
          reject(e)
        })
    })
  })
}

核心思路:

  1. Promise.myAll()返回的肯定是一個promise物件,所以可以直接寫一個return new Promise((resolve, reject) => {})(這應該是一個慣性思維)
  2. 遍歷傳入的引數,用Promise.resolve()將引數"包一層",使其變成一個promise物件
  3. 關鍵點是何時"決議",也就是何時resolve出來,在這裡做了計數器(count),每個內部promise物件決議後就將計數器加一,並判斷加一後的大小是否與傳入物件的數量相等,如果相等則呼叫resolve(),如果任何一個promise物件失敗,則呼叫reject()方法。

一些細節:

  1. 官方規定Promise.all()接受的引數是一個可遍歷的引數,所以未必一定是一個數組,所以用Array.from()轉化一下
  2. 使用for…of進行遍歷,因為凡是可遍歷的變數應該都是部署了iterator方法,所以用for…of遍歷最安全

Promise.race()

回顧一下race的用法

var promise1 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 500, 'one');
});

var promise2 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2]).then(function(value) {
  console.log(value);
  // Both resolve, but promise2 is faster
});
// expected output: "two"

有了Promise.all()的鋪墊,race就好寫多了。

Promise.myRace = function (iterators) {
    return new Promise((resolve, reject) => {
        for (let p of iterators) {
            Promise.resolve(p)
                .then((result) => {
                    resolve(result)
                })
                .catch(e => {
                    reject(e)
                })
        }
    })
}

核心思路:

  1. 誰先決議那麼就返回誰,所以將all的計數器和邏輯判斷全部去除掉就可以了。

Promise.prototype.finally()

回顧一下正常用法。

finally() 方法返回一個Promise。在promise結束時,無論結果是fulfilled或者是rejected,都會執行指定的回撥函式。這為在Promise是否成功完成後都需要執行的程式碼提供了一種方式。

這避免了同樣的語句需要在then()catch()中各寫一次的情況。

let isLoading = true;

fetch(myRequest).then(function(response) {
    var contentType = response.headers.get("content-type");
    if(contentType && contentType.includes("application/json")) {
      return response.json();
    }
    throw new TypeError("Oops, we haven't got JSON!");
  })
  .then(function(json) { /* process your JSON further */ })
  .catch(function(error) { console.log(error); })
  .finally(function() { isLoading = false; });

手寫實現

Promise.prototype.myFinally = function finallyPolyfill(callback) {
    return this.then(function(value) {
            return Promise.resolve(callback()).then(function() {
                return value;
            });
        }, function(reason) {
            return Promise.resolve(callback()).then(function() {
                Promise.reject(reason);
            });
        });
};

核心思路:

  1. 當前this指向的是當前Promise物件,所以可以直接用this.then()
  2. then回撥中的兩個引數,一個是成功時的回撥,另一個是失敗是的回撥,利用這個兩個引數處理當前promise物件的兩種不同情況
  3. 無論如何都要呼叫傳入的callback函式,並且將當前promise的決議值繼續傳遞下去

一些細節:

callback傳入的有可能仍然是一個Promsie物件,如果真的是Promise物件,要等該promise決議之後才能執行之後then()方法,但是這個then()中拿到的是finally()之前的決議值,有種"決議值穿透"的感覺。

PS:我在網上找到了最權威的寫法,毫無破綻 https://github.com/matthew-andrews/Promise.prototype.finally/blob/master/finally.js