javascript 請求-Promise的分析和實現

Promise 在平常使用中很是普遍,本身也是零零散散的學習了一部分,直到有一天本身查看axios的源碼時,發現裏面大量的應用了Promise的函數,決定系統的學習一下。本文主要是根據Promise/A+的規範,進行Promise的學習和實現;ios

Promise介紹

Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。 所謂Promise,簡單說就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。從語法上說,Promise 是一個對象,從它能夠獲取異步操做的消息。Promise 提供統一的 API,各類異步操做均可以用一樣的方法進行處理。git

特色:

  • 對象不受外界影響,共包含三種狀態pengding(進行中,初始狀態),fulfilled(已成功)、reject(已失敗);只有異步操做結果,纔可以改變當前的狀態,其餘的手段沒法改變
  • 一旦狀態改變,就不會在變,其中只包含兩種可能的狀態變化 pending->fulfilled和pending變爲reject,只要有這兩種狀況發生,狀態就會凝固,不會在發生改變了

缺點:

  • 沒法取消``promise````,一旦新建就會馬上執行沒法中途取消;
  • 若是不設置回調函數,內部發生錯誤不會反應到外部;
  • 處於pending的狀態時候,沒法獲得目前是哪個階段;

promise的手寫實現

想要實現某個內容,就應該明確Promise的方法使用; 使用方法可參照:es6.ruanyifeng.com/#docs/promi…
本次手寫也是參照此用法,對輸入和輸出進行的控制
Promise 做爲一個構造函數,其在new的時候就是馬上進行執行,根據其屬性和行爲去構建基礎執行流程,new Promise()自己是一個同步的函數,而Promise.then纔是異步的函數es6

基礎狀態確認

查看Promise A+規範對於狀態的要求
Promies/A+規範要求:github

  • Promise的狀態是pending的時候,可能會轉化到 fulfilled或者rejected狀態
  • Promise狀態是filfilled的時候
    • 不能轉化成其餘的狀態
    • 必須返回一個value,而且這個value保持不變
  • Promise的狀態是reject的時候
    • 也沒法轉變成其餘的狀態
    • 必須返回一個失敗的緣由,

定義Promise的常量

const PENDING = 'pending';
const RESOLVED = 'fulfilled'; //成功
const REJECTED = 'rejected' //失敗
複製代碼

建立Promise 的構造函數

關鍵點面試

  • 定義當前狀態變量status
  • 定義resolvereject來接收成功和失敗時候狀態更改
  • new Promise時候返回成功和失敗狀態;
//建立Promise的基本類
class Promise {
  //看這個屬性 可以在原型上使用 看屬性是否公用
  constructor(executor) {
    this.status = PENDING;
    //成功的值
    this.value = undefined;
    //失敗的緣由
    this.reason = undefined;
 
    //回調函數存儲器 主要解決異步處理流程
    this.onReslovedCb = []; //成功回調
    this.onRejectedCb = []; //失敗回調

    //成功函數
    let resolve = (value) => {
    //只有在pending的時候才能夠調用
      if (this.status == PENDING) {
        this.value = value;
        this.status = RESOLVED;
        this.onReslovedCb.forEach(fn => fn())
      }
    }
    //失敗函數
    let reject = (reason) => {
        //只有在pending的時候才能夠調用
      if (this.status == PENDING) {
        this.reason = reason;
        this.status = REJECTED
        this.onRejectedCb.forEach(fn => fn())
      }
    }
    try {
      //執行器 默認會當即執行
      executor && executor(resolve, reject);
    } catch (e) {
      //執行的時候出現錯誤
      reject(e)
    }
  }
}
複製代碼

Promise.prototype.then

關鍵點then 方法是Promise已經失敗/成功時候調用 先看下promise/A+的規範 粗略列舉了重要的內容編程

promise.then(onFulfilled, onRejected)
複製代碼
  • onFulfilled,onRejected是then兩個參數
    • 若是onFulfilled 不是函數,將會被直接忽略
    • 同理 onRejected不是函數,也會被直接忽略
  • onFulfilled 是函數
    • 當promise的狀態是成功狀態的時候,其將會被回調,返回的value會是第一個參數
    • 不能被進行調用在其餘的狀態,並且只能調用一次
  • onRejected是函數的時候
    • 當promise狀態是失敗時候被調用,失敗的緣由是第一個參數
    • 不能在其餘的狀態下被調用,只能被調用一次
  • then方法可以在同一個promise上可以被調用屢次
    • 若是當前promise的狀態是fulfilled/onRejected,全部的then回調都必須按照他們的調用初始順序執行
  • then方法 必須返回一個promise

其中onFulfilledonRejected是咱們在外部調用的時候傳遞進行的回調函數;axios

then(onfulfilled, onrejected) {
    //1. 參數是可選則的參數 須要進行判斷是否存在
    onfulfilled = typeof onfulfilled == 'function' ? onfulfilled : data => data
    onrejected = typeof onrejected == 'function' ? onrejected : error => {
      throw error
    }
    // 2.若是存在回調函數,則執行內部的回調函數,裏面的函數會馬上執行
    let promise2 = new Promise((resolve, reject) => {
      //2.1 Promise 返回成功的時候 
      if (this.status == RESOLVED) { 
        // 定時器處理異常 爲了保障promise2已經用完了
         setTimeout(() => {
          //try 執行函數的時候會報錯 在then裏面的數據
          try {
            //x 須要判斷是不是promise和規整化 
            let x = onfulfilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      }
      //2.1-失敗的時候
      if (this.status == REJECTED) {
        setTimeout(() => {
          try {
            let x = onrejected && onrejected(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      }
      // 2,1- pending 若是當前是pending 表示異步的請求還沒回來,進行收集內容
      if (this.status == PENDING) {
        //若是是異步 先訂閱好
        this.onReslovedCb.push(() => {
          //todo... 
          setTimeout(() => {
            try {
              let x = onfulfilled(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
        this.onRejectedCb.push(() => {
          //todo...

          setTimeout(() => {
            try {
              let x = onrejected(this.reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }

          }, 0)
        })
      }

    })
    return promise2;
  }
複製代碼

then方法返回一個Promise的函數,注意在進行promise2的建立的時候,咱們在進行處理時候可能獲取的到的是underfined的promise2,所以須要開闢宏任務,promise2建立完成的時候在進行調用,而在進行處理的時候,咱們在onfulfilled、和onrejected獲得的參數可能不一樣,他們收到的參數可能爲幾種
api

.then(data=>{
    return value;
},err=>{
    return value
}) 
複製代碼
  • value 多是一個Promise,須要執行當前的Promise
  • value 字符串
  • value爲使用者輸入,可能存在的值也是不肯定的,所以須要進行判斷,而onfulfilled、onRejected調用後的結果也是不肯定的,所以須要進行類型的判斷;

then的關鍵點在於resolvePromise 的函數,這個函數的作用究竟是什麼呢?數組

//判斷then裏面的函數返回值來進行判斷 x表示當前onreject
//promise都遵循的規範,所以須要進行兼容寫法
function resolvePromise(promise2, x, resolve, reject) {
  //判斷當前的x是否是promise 是否是同一個 若是是同一個 就不要等待來了 
  if (promise2 === x) {
    return reject(new TypeError("調用存在錯誤"))
  }
  //若是x是對象或者函數 判斷數據類型 
  /** * typeof 基本類型 * constructor * instanceof 判斷實例 * Object.toString */
  if (typeof x === 'object' && typeof x !== null || typeof x == 'function') {
    let called; //內部測試的時候,會成功和失敗都調用一下 
    try {
      //取返回結果 then有可能經過defineProperty定義的
      let then = x.then
      //當前存在then方法 姑且是Promise
      if (typeof then === 'function') {
        //綁定this 到返回的x上,保證不用再次取then的值
        then.call(x, y => {
          if (called) return;
          called = true; //防止屢次調用成功和失敗
          //y可能仍是promise //採用promise的成功結果向下傳遞
          resolvePromise(promise2, y, resolve, reject)
        }, r => {
          if (called) return;
          called = true;
          reject(r) //採用失敗結果鄉下傳遞
        }) //保證再次取到then的值
      } else {
        //說明x就是一個普通的對象 直接成功便可
        resolve(x)
      }
    } catch (e) {
      //promise 失敗 還能進行調用成功
      //是一個普通的值 直接讓promise2成功便可
      if (called) return;
      called = true;
      reject(e)
    }
  } else {
    return resolve(x)
  }
}
複製代碼

在then方法執行完後,Promise的實例狀態就會改變成resolved、或者reject,此時then方法須要兼容一異步的調用類型.所以,當進入then函數後,若是當前的promise的狀態仍然是Pending,則表示當前結果尚未返回,所以須要增長onRejectedCb、onReslovedCb用來存儲當前的執行函數,一旦某一個狀態改變,則進行調用該存儲列表中的數據,進行回調;promise

注意點

  • then方法中的狀態收集和修改
  • onfulfilled/onrejected 返回的參數類型

Promise.prototype.finally

不管Promise返回成功仍是失敗,都會執行該方法

  • finally()方法用於指定無論 Promise 對象最後狀態如何,都會執行的操做;
  • finally方法的回調函數不接受任何參數,這意味着沒有辦法知道,前面的 Promise 狀態究竟是fulfilled仍是rejected。這代表finally方法裏面的操做,應該是與狀態無關的,不依賴於 Promise 的執行結果。

所以能夠綁定此事件在當前promise實例的then方法上,在成功的時候回調傳入的函數,在失敗的時候也進行回調傳入的參數;

/** * finally 函數 promise m每次執行後都會進行執行 * @param {*} cb */
Promise.prototype.finally = function (cb) {
  //finally 傳入函數,不管成功或者失敗都會執行 
  return this.then(data => {
    //Promise.resolve 能夠等待這個promise完成
    return Promise.resolve(cb().then(() => data))
  }, err => {
      //失敗的時候也執行
    return Promise.reject(cb().then(() => {
      throw err
    }))
  })
}
複製代碼

Promise.prototype.catch

Promise.prototype.catch()方法是.then(null, rejection).then(undefined, rejection)的別名,用於指定發生錯誤時的回調函數。

//異常處理 用於指定發生錯誤時的回調函數。
//promise拋出一個錯誤,就被catch()方法指定的回調函數捕獲
Promise.prototype.catch = function (onRejected) {
  return this.then(undefined, onRejected)
}
複製代碼

Promise.all

Promise.all可用於接收一個數組做爲參數,參數能夠不是數組,可是必須有Iterator接口,且返回的每一個成員都是Promise的實例,他的結果是根據傳入的數據進行變化的

const p = Promise.all([p1, p2, p3]);
複製代碼
  • 只有p一、p二、p3的狀態都變成fulfilled,p的狀態纔會變成fulfilled,此時p一、p二、p3的返回值組成一個數組,傳遞給p的回調函數。
  • 只要p一、p二、p3之中有一個被rejectedp的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。
/** * 所有成功才能成功,一個失敗纔會失敗 * promiseList 表示當前傳遞的數組對象 */
Promise.all = function (promiseList) {

  return new Promise((resolve, reject) => {
    let arr = [];
    let index = 0;
    //解決多個異步併發的問題 計數器
    function proceessData(key, value) {
      arr[key] = value;
      if (++index == promiseList.length) {
        resolve(arr)
      }
    }
    for (let i = 0; i < promiseList.length; i++) {
      let current = promiseList[i];
      if (isPromise(current)) {
        current.then((data) => {
          proceessData(i, data)
        }, (err) => {
          console.log("data")
          reject(err)
        })
      } else {
        proceessData(i, current)
      }
    }
  })
}

function isPromise(value) {
  if ((typeof value === 'object' && value !== null) || typeof value === 'function') {
    if (typeof value.then == 'function') {
      return true
    }
  }
  return false;
}
複製代碼

Promise.race

Promise.race()方法一樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。從字面意義上而言,「管道」返回最早獲得的那個

const p = Promise.race([p1, p2, p3]);
複製代碼
  • 只要p一、p二、p3之中有一個實例率先改變狀態,p的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。
/** * 方法一樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。 * @param {array} promiseList 傳遞的參數列表對象 */
Promise.race = function (promiseList) {
  // console.log(promiseList)
  //將values中的內容包裝成promise的 
  if (!Array.isArray(promiseList)) {
    return Promise.resolve();
  }
  promiseList = promiseList.map(item => {
    return !isPromise(item) ? Promise.resolve(item) : item;
  });
  // 有一個實例率先改變狀態則進行操做 
  return new Promise((resolve, reject) => {
    promiseList.forEach((pro, index) => {
      pro.then(res => {
        resolve(res)
      }, err => {
        reject(err)
      })
    })
  })
}
複製代碼

Promise.allSettled

Promise.allSettled()方法接受一組 Promise 實例做爲參數,包裝成一個新的 Promise 實例。只有等到全部這些參數實例都返回結果,不論是fulfilled仍是rejected,包裝實例纔會結束。該方法由 ES2020 引入。

/** * 方法接受一組 Promise 實例做爲參數,包裝成一個新的 Promise 實例。只有等到全部這些參數實例都返回結果, * 不論是fulfilled仍是rejected,包裝實例纔會結束 */
Promise.allSettled = function (promiseList) {
  return new Promise((resolve, reject) => {
    let index = 0;
    let arr = [] ;
    //用於記錄當前的promise的執行狀態
    function recordRequest(key, value) {
      index++;
      arr[key] = value;
      //選擇這種計數的方式,主要是考慮存在異步的流程,等待全部流程都執行完成後在結束
      if (index == promiseList.length) {
        resolve(arr)
      }
    }
    for (let i = 0; i < promiseList.length; i++) {
      current = promiseList[i]
      if (isPromise(current)) {
        current.then((data) => {
        //每執行完成一個,就去增長記錄
          recordRequest(i, {
            status: 'resolve',
            value: data
          })
        }, (err) => {
           //失敗的promise也記錄
          recordRequest(i, {
            status: 'reject',
            reason: err
          })
        })
      } else {
        recordRequest(i, {
          status: '',
          value: current
        })
      }
    }
  })
  }
複製代碼

Promise.any

Promise.any()方法接受一組 Promise 實例做爲參數,包裝成一個新的 Promise 實例。只要參數實例有一個變成fulfilled狀態,包裝實例就會變成fulfilled狀態;若是全部參數實例都變成rejected狀態,包裝實例就會變成rejected狀態。該方法目前是一個第三階段的提案 。

  • 一失敗全失敗
  • 全部成功才成功
/** * Promise.any()方法接受一組 Promise 實例做爲參數,包裝成一個新的 Promise 實例。只要參數實例有一個變成fulfilled狀態,包裝實例就會變成fulfilled狀態;若是全部參數實例都變成rejected狀態,包裝實例就會變成rejected狀態。該方法目前是一個第三階段的提案 。 * @param {*} promiseList promise的參數列表 */
Promise.any = function(promiseList){
  promiseList = promiseList.map(item => {
    return !isPromise(item) ? Promise.resolve(item) : item;
  });
  let index = 0; 
  let result=[]
  return new Promise((resolve,reject)=>{
    for (let i = 0; i < promiseList.length; i++) {
      current = promiseList[i]
      if (isPromise(current)) {
        current.then((data) => {
          resolve(data)
        }, (err) => { 
          index++; 
          result.push(err)
          if(index == promiseList.length){
            reject(err);
          } 
        })
      } 
    }
  })
}
複製代碼

Promise.resolve

有時須要將現有對象轉爲 Promise 對象,Promise.resolve()方法就起到這個做用。會返回一個狀態爲Resolved狀態的promise
Promise.resolve(value),其中value的值包含好多種

  • 參數是一個 Promise 實例
  • 參數是一個thenable對象
  • 參數不是具備then()方法的對象,或根本就不是對象
  • 不帶有任何參數

參數的類型可能存在幾種狀況

/** * Promis.resolve 函數 * @param {*} values 傳遞進來的變量函數 */
Promise.resolve = function (values) {
  //1.參數是一個 Promise 實例 將原封不動的返回
  if (values instanceof Promise) {
    return values;
  }
  return new Promise((resolve, reject) => {
    //2.參數是一個含有then對象 具備then方法
    //Promise.resolve()方法會將這個對象轉爲 Promise 對象,而後就當即執行thenable對象的then()方法。
    if (isPromise(values)) {
      values.then(resolve, reject);
    } else {
      //3.參數不是具備then()方法的對象,或根本就不是對象 若是參數是一個原始值,或者是一個不具備then()方法的對象,則Promise.resolve()方法返回一個新的 Promise 對象,狀態爲resolved。
      //4.參數不是具備then()方法的對象,或根本就不是對象
      //5.不帶有任何參數 
      resolve(values)
    }
  })
}
複製代碼

Promise.reject

Promise.reject(reason)方法也會返回一個新的 Promise 實例,該實例的狀態爲rejected。

/** * //Promise.reject(reason)方法也會返回一個新的 Promise 實例,該實例的狀態爲rejected。 * 參數爲values字符串 */
Promise.reject = function (values) {
  return new Promise((resolve, reject) => {
      reject(values)
  })
}
複製代碼

Promise.try

實用場景: 不知道或者不想區分,函數f是同步函數仍是異步操做,可是想用 Promise 來處理它。由於這樣就能夠無論f是否包含異步操做,都用then方法指定下一步流程,用catch方法處理f拋出的錯誤。通常就會採用下面的寫法。
因爲Promise.try爲全部操做提供了統一的處理機制,因此若是想用then方法管理流程,用Promise.try包裝一下,能夠更好地管理異常。

Promise.try = function (fn, argumnts = null, ...args) {
  if (typeof fn == 'function') {
  //馬上執行fn函數並進行調用返回
    return new Promise(resolve => resolve(fn.apply(argumnts, args)))
  } else {
    const err = new TypeError(`${typeof fn} ${fn} is not a function`);
    return Promise.try(() => {
      throw err
    });
  }
}
複製代碼

源碼位置

面試頻點

  • 和setTimeout 進行 打印輸出判斷(微任務、宏任務執行順序) 很是高頻
    • let p = new Promise() 自己同步執行
    • p.then()纔是異步的執行
  • Promise.then 的實現
  • Promise實現多個請求同時進行
  • Promise的三種狀態變化
  • Promise的常見api
  • Promise解決的問題痛點

參考文檔

相關文章
相關標籤/搜索