Promise基礎到手寫(使用+構造)

Promise簡介

Promise 最先出現是爲了解決編程中的異步行爲致使的回調地獄。在沒有 Promise 以前,對於函數的異步行爲,通常採用回調函數的方式,在一個函數調用結束觸發回調函數,這樣會致使多層級的回調函數,難以維護。 Promise 有兩個參數,一個成功回調,一個失敗回調,因此在 Promise 外部,能夠準確的得到成功和失敗的時機,而且 Promise 支持鏈式調用,這樣能夠方便的進行屢次調用,可是永遠都是單層級,便於維護。javascript

兩種異步行爲對比

回調地獄方式html

var sayhello = function (name, callback) {
  setTimeout(function () {
    console.log(name);
    callback();
  }, 1000);
}
sayhello("first", function () {
  sayhello("second", function () {
    sayhello("third", function () {
      console.log("end");
    });
  });
});
//輸出: first second third end
複製代碼

Promise 寫法java

let sayHello = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log("first");
    resolve();
  }, 1000);
});
sayHello
  .then(() => {
    console.log("second");
  })
  .then(() => {
    console.log("third");
  })
  .then(() => {
    console.log("end");
  });
複製代碼

咱們能夠發現 Promise 無論有多少次邏輯處理,每一次只有一層,清晰可見,不像回調同樣,層層嵌套,難以理清。編程

Promise 基礎

Promise 實例數組

let promise = new Promise(
  function(resolve, reject) { 	// executor(執行者)
  	setTimeout(()=>{		
    resolve("done"),1000);
});
複製代碼

咱們只須要 new Promise便可建立一個 Promise,建立即馬上調用其中的執行者。 executor 接受兩個參數:resolve 和 reject 。這些是JavaScript 引擎預約義的,不要咱們建立,咱們只須要在咱們想要告知的狀態中調用對應的方法便可。promise

let promise = new Promise(function(resolve, reject) {
  resolve("done");

  reject(new Error("…")); // 被忽略
  setTimeout(() => resolve("…")); // 被忽略
});
複製代碼

這兒只能有一個結果或一個 error executor 只能調用一個 resolve 或一個 reject。任何狀態的更改都是最終的。 全部其餘的再對 resolvereject 的調用都會被忽略 而且,resolve/reject 只須要一個參數(或不包含任何參數),而且將忽略額外的參數markdown

那麼咱們會疑問,咱們費這麼大工夫,在 Promise 內部作這麼多操做,最後使他產生一個狀態是爲了什麼,他失敗與否和咱們以前的回調地獄有什麼關係?異步

state 和 result 都是內部的
Promise 對象的 state 和 result 屬性都是內部的。
咱們沒法直接訪問它們。但咱們能夠對它們使用 .then/.catch/.finally 方法。
咱們在下面對這些方法進行了描述。
複製代碼

上面咱們使用 Promise 生產了一個成功或者失敗的結果,能夠經過使用 .then.catch 和 .finally 方法爲消費函數進行結果接收。函數

then

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve("done!"), 1000);
});

// resolve 運行 .then 中的第一個函數
promise.then(
  result => alert(result), // 1 秒後顯示 "done!"
  error => alert(error) // 不運行
);
複製代碼

.then 的第一個參數是一個函數,該函數將在 promise resolved 後運行並接收結果。 .then 的第二個參數也是一個函數,該函數將在 promise rejected 後運行並接收 error。 若是咱們只對成功完成的狀況感興趣,那麼咱們能夠只爲 .then 提供一個函數參數:oop

let promise = new Promise(resolve => {
  setTimeout(() => resolve("done!"));
}, 1000);

promise.then(alert); // 1 秒後顯示 "done!"
複製代碼

.catch(f) 調用是 .then(null, f) 的徹底的模擬,它只是一個簡寫形式。簡單說,catch就是一個接收錯誤結果的方法。 咱們能夠對 settled 的 promise 附加處理程序 若是 promise 爲 pending 狀態,.then/catch/finally 處理程序(handler)將等待它。不然,若是 promise 已是 settled 狀態,它們就會運行 自測案例 寫一個 3s 後彈窗的 Promise

function delay(ms) {
  // 你的代碼
  return new Promise(resolve,reject){
  	setTimeout(resolve,3000)
  }
 }

delay(3000).then(() => alert('runs after 3 seconds'));
複製代碼

catch

若是隻要失敗回調,那麼只須要將 then 的第一個參數設置爲null, 也可使用 catch ,這裏面能夠接受 reject 的結果

let promise = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});

// .catch(f) 與 promise.then(null, f) 同樣
promise.catch(alert); // 1 秒後顯示 "Error: Whoops!"

複製代碼

finally

不管結果如何,最後都會執行這裏面的函數。 **finally()** 方法返回一個Promise。在promise結束時,不管結果是fulfilled或者是rejected,都會執行指定的回調函數。這爲在Promise是否成功完成後都須要執行的代碼提供了一種方式。 這避免了一樣的語句須要在then()catch()中各寫一次的狀況。

構建 Promise

在我瞭解完 Promise 後,我對如何實現他很是感興趣,因而我試着本身來構建一個 Promise。 首先咱們要分析一下咱們的需求,咱們要獲得什麼,要實現哪些功能,肯定目標。

  1. 咱們要實現一個名叫 Promise 的類。
  2. 類裏咱們要實現一個 resolve 成功通知。
  3. 類裏咱們要實現一個 reject失敗通知。
  4. executor 馬上執行。
  5. 咱們還要實現一個能夠拿到結果的 then。
  6. 一個捕獲錯誤的 catch。
  7. 一個無論結果的 finally。

Promise.png 咱們按照上圖,分爲兩個大步驟,開始進行實現咱們本身的 Promise。

首先構造 Promise 類。

  1. 初始化階段,咱們考慮到 Promise 一共有三種狀態,兩個結果。因此咱們要初始化狀態和結果。
  2. 而後咱們發送成功和失敗的信息時,要改變狀態,而且保存結果。
class Promise {
  constructor(executor) {
    if (typeof executor !== "function") {
      console.log("參數不合法");
    } else {
      this.status = "pending";
      this.value = undefined;
      this.error = undefined;
      //自動運行Promise中的函數
      try {
        //將resolve和reject函數給使用者
        executor(resolve, reject);
      } catch (e) {
        //若是在函數中拋出異常則將它注入reject中
        reject(e);
      }
    }
  }
  resolve(data) {
    //成功觸發
    if (this.status === "pending") {
      this.status = "fulfilled";
      this.value = data;
    }
  }
  reject(data) {
    //失敗觸發
    if (this.status === "pending") {
      this.status = "rejected";
      this.error = data;
    }
  }
  then() {}
  catch() {}
  finally() {}
}

複製代碼

咱們實現了上述幾個目標,接下來咱們要實現接受結果信息的方法。

  1. then 接受兩個參數,第一個將在 promise resolved 後運行並接收 value 。第二個將在 promise reject 後運行並接收 error。
  2. catch 只接受一個函數,promise reject 後運行並接收 error。
  3. finally 不管結果如何都會執行。
class Promise {
  constructor(executor) {
    if (typeof executor !== "function") {
      console.log("參數不合法");
    } else {
      this.status = "pending";
      this.value = undefined;
      this.error = undefined;
      //自動運行Promise中的函數
      try {
        //將resolve和reject函數給使用者
        executor(this.resolve, this.reject);
      } catch (e) {
        //若是在函數中拋出異常則將它注入reject中
        this.reject(e);
      }
    }
  }
  resolve = (data) => {
    //成功觸發
    if (this.status === "pending") {
      this.status = "fulfilled";
      this.value = data;
    }
  };
  reject = (data) => {
    //失敗觸發
    if (this.status === "pending") {
      this.status = "rejected";
      this.error = data;
    }
  };
  then(onFulfilled, onRejected) {
    if (this.status === "fulfilled") {
      onFulfilled(this.value);
    }
    if (this.status === "rejected") {
      onRejected(this.error);
    }
  }
  catch(onRejected) {
    if (this.status === "rejected") {
      onRejected(this.error);
    }
  }
  finally(onFinally) {
    if (this.status !== "pending") {
      onFinally();
    }
  }
}

複製代碼

這樣,咱們就完成了一個簡易版的 Promise。 咱們來將文件引入測試一下,看看結果如何。

<!DOCTYPE html>
<html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <script src="./index.js"></script> <script> let promise = new Promise((resolve, reject) => { resolve("coolFish!"); }); promise.then( (data) => { console.log("成功" + data); }, (data) => { console.log("失敗" + data); } ); </script> </body> </html>


複製代碼

結果和 Promise 同樣,能夠實現成功和失敗的不一樣操做,接下來咱們要開始擴展它的功能,從可支持鏈式調用開始。

Promise.then

首先咱們明確 promise.then(onFulfilled, onRejected ) 作的事情

  1. 入參判斷,處理 onFulfilled 或者 onRejected 不是函數的狀況。
  2. 建立而且返回一個 promise 實例。
  3. 將 onFulfilled 和 onRejected 添加到事件隊列(根據 promise 的狀態來決定如何處理)。
  4. 狀態爲 fulfilled 執行 onFulfilled 。
  5. 狀態爲 rejected 則執行 onRejected。
  6. 若是沒有作出決議,則添加進事件隊列。
then(onFulfilled, onRejected) {
    //建立並返回一個Promise實例
    return new Promise((resolve, reject) => {
      let wrapOnFulfilled = () => {
        setTimeout(() => {
          try {
            console.log("wrapOnFulfilled");
            let x = onFulfilled(this.value);
            resolve(x);
          } catch (error) {
            reject(error);
          }
        }, 0);
      };
      let wrapOnRejected = () => {
        setTimeout(() => {
          try {
            console.log("wrapOnRejected");
            let x = onRejected(this.error);
            resolve(x);
          } catch (error) {
            reject(error);
          }
        }, 0);
      };
      if (this.status === "fulfilled") {
        wrapOnFulfilled();
      } else if (this.status === "rejected") {
        wrapOnRejected();
      } else {
        this.onFulfilledCallbacks.push(wrapOnFulfilled);
        this.onRejectedCallbacks.push(wrapOnRejected);
      }
    });
  }

複製代碼

Promise.all

首先咱們先明確目標 Promise.all 接受一個 promise 數組做爲參數(從技術上講,它能夠是任何可迭代的,但一般是一個數組)並返回一個新的 promise。 當全部給定的 promise 都被 settled 時,新的 promise 纔會 resolve,而且其結果數組將成爲新的 promise 的結果。 咱們來看一張流程圖,而後咱們按照流程圖來實現咱們的代碼

PromiseAll.png

all(promises) {
    return new Promise((resolve, reject) => {
      // 若是Promise.all接收到的是一個空數組([]),它會當即決議。
      if (!promises.length) {
        resolve([]);
      }
      let result = [];
      let resolvedPro = 0;
      for (let index = 0, length = promises.length; index < length; index++) {
        Promise.resolve(promises[index]).then(
          (data) => {
            // 注意,這裏要用index賦值,而不是push。由於要保持返回值和接收到的promise的位置一致性。
            result[index] = data;
            if (++resolvedPro === length) {
              resolve(result);
            }
          },
          (error) => {
            reject(error);
          }
        );
      }
    });
  }

複製代碼

Promise.race

// 須要注意的是,若是Promise.race接收到的是一個空數組([]),則會一直掛起,而不是當即決議。
Promise.race = function(promises) {
  return new Promise((resolve, reject) => {
    promises.forEach((promise) => {
      Promise.resolve(promise).then(resolve, reject);
    });
  });
};

複製代碼

Promise.allSettled

// Promise.allSettled 返回一個在全部給定的promise都已經fulfilled或rejected後的promise,
// 並帶有一個對象數組,每一個對象表示對應的promise結果。
Promise.allSettled = function(promises) {
  return new Promise((resolve, reject) => {
    if (!promises.length) {
      resolve([]);
    }

    let result = [];
    let resolvedPro = 0;
    for (let index = 0, length = promises.length; index < length; index++) {
      Promise.resolve(promises[index])
        .then((data) => {
          // 注意,這裏要用index賦值,而不是push。由於要保持返回值和接收到的promise的位置一致性。
          result[index] = {
            status: FULFILLED_STATE,
            value: data,
          };
          if (++resolvedPro === length) {
            resolve(result);
          }
        })
        .catch((error) => {
          result[index] = {
            status: REJECTED_STATE,
            reason: error,
          };
          if (++resolvedPro === length) {
            resolve(result);
          }
        });
    }
  });
};

複製代碼
相關文章
相關標籤/搜索