promise源碼詳解,助力你輕鬆掌握promise

煉獄般的回調

在沒有出來promise以前,爲了拿到函數的回調結果,咱們不得不使用callback function,這種代碼的維護和理解是至關噁心了! 下面咱們來看看這個噁心的回調程序員

let fs = require('fs')
fs.readFile('./test.txt','utf8',function(err,data){
  fs.readFile(data,'utf8',function(err,data){
    fs.readFile(data,'utf8',function(err,data){
      console.log(data)
    })
  })
})

複製代碼

人類仍是頗有智慧的,終於在程序員的努力之下,咱們的好對象Promise出現了,他幫咱們解決了噁心的回調,消除了咱們心中多年的痛,下面請欣賞promise帶來高大上的代碼數組

let fs = require('fs')
function readFile(url){
  return new Promise((resolve,reject)=>{
    fs.readFile(url,'utf8',function(error,data){
      error && reject(error)
      resolve(data)
    })
  })
}

readFile('./test.txt').then(data=>{
  return read(data)
}).then(data=>{
  return read(data)  
}).then(data=>{
  console.log(data)
})

複製代碼

走進promise的心裏,讓你真的更懂它

  • Promise/A+ 首先咱們要知道本身手寫一個Promise,應該怎麼去寫,誰來告訴咱們怎麼寫,須要遵循什麼樣的規則。固然這些你都不用擔憂,其實業界都是經過一個規則指標來生產Promise的。讓咱們來看看是什麼東西。傳送門☞Promise/A+promise

  • constructor 咱們先聲明一個類,叫作Promise,裏面是構造函數。請看下面代碼實現bash

class Promise{
  constructor(executor){
    //控制狀態,使用了一次以後,接下來的都不被使用
    this.status = 'pendding'
    this.value = undefined
    this.reason = undefined

    //定義resolve函數
    let resolve = (data)=>{
      //這裏pendding,主要是爲了防止executor中調用了兩次resovle或reject方法,而咱們只調用一次
      if(this.status==='pendding'){
        this.status = 'resolve'
        this.value = data
      }
    }

    //定義reject函數
    let reject = (data)=>{
      if(this.status==='pendding'){
        this.status = 'reject'        
        this.reason = data
      }
    }

    //executor方法可能會拋出異常,須要捕獲
    try{
      //將resolve和reject函數給使用者      
      executor(resolve,reject)      
    }catch(e){
      //若是在函數中拋出異常則將它注入reject中
      reject(e)
    }
  }
}

複製代碼

接下來,咱們開始分析上面代碼的做用以及底層原理異步

1.executor:這是實例Promise對象時在構造器中傳入的參數,通常是一個function(resolve,reject){} 2.status:``Promise的狀態,一開始是默認的pendding狀態,每當調用道resolve和reject方法時,就會改變其值,在後面的then方法中會用到 3.value:resolve回調成功後,調用resolve方法裏面的參數值 4.reason:reject回調成功後,調用reject方法裏面的參數值 5.resolve:聲明resolve方法在構造器內,經過傳入的executor方法傳入其中,用以給使用者回調 6.reject:聲明reject方法在構造器內,經過傳入的executor方法傳入其中,用以給使用者回調函數

  • then

then方法是Promise中最爲重要的方法,他的用法你們都應該已經知道,就是將Promise中的resolve或者reject的結果拿到,那麼咱們就能知道這裏的then方法須要兩個參數,成功回調和失敗回調,直接看代碼ui

then(onFufilled,onRejected){  
  if(this.status === 'resolve'){
    onFufilled(this.value)
  }
  if(this.status === 'reject'){
    onRejected(this.reason)
  }
}

複製代碼

這裏主要作了將構造器中resolve和reject的結果傳入onFufilled和onRejected中,注意這兩個是使用者傳入的參數,是個方法this

異步的Promise

以前咱們只是處理了同步狀況下的Promise,簡而言之全部操做都沒有異步的成分在內。那麼若是是異步該怎麼辦?url

  • callback

最先處理異步的方法就是callback,就至關於我讓你幫我掃地,我會在給你發起任務時給你一個手機,以後我作本身的事情去,不用等你,等你掃完地就會打手機給我,誒,我就知道了地掃完了。這個手機就是callback,回調函數。spa

首先咱們須要改一下構造器裏的代碼,分別添加兩個回調函數的數組,分別對應成功回調和失敗回調。他們的做用是當成功執行resolve或reject時,執行callback。

//存放成功回調的函數
this.onResolvedCallbacks = []
//存放失敗回調的函數
this.onRejectedCallbacks = []

let resolve = (data)=>{
  if(this.status==='pendding'){
    this.status = 'resolve'
    this.value = data
    //監聽回調函數
    this.onResolvedCallbacks.forEach(fn=>fn())
  }
}
let reject = (data)=>{
  if(this.status==='pendding'){
    this.status = 'reject'        
    this.reason = data
    this.onRejectedCallbacks.forEach(fn=>fn())
  }
}

複製代碼

而後是then須要多加一個狀態判斷,當Promise中是異步操做時,須要在咱們以前定義的回調函數數組中添加一個回調函數。

if(this.status === 'pendding'){
  this.onResolvedCallbacks.push(()=>{
    // to do....
    let x = onFufilled(this.value)
    resolvePromise(promise2,x,resolve,reject)
  })
  this.onRejectedCallbacks.push(()=>{
    let x = onRejected(this.reason)
    resolvePromise(promise2,x,resolve,reject)
  })
}
複製代碼
  • resolvePromise

這也是Promise中的重頭戲,我來介紹一下,咱們在用Promise的時候可能會發現,當then函數中return了一個值,咱們能夠繼續then下去,不過是什麼值,都能在下一個then中獲取,還有,當咱們不在then中放入參數,例:promise.then().then(),那麼其後面的then依舊能夠獲得以前then返回的值,可能你如今想很迷惑。咱們先來看下面的代碼,我逐步爲你一個個的解答

then(onFufilled,onRejected){
    //解決onFufilled,onRejected沒有傳值的問題
    onFufilled = typeof onFufilled === 'function'?onFufilled:y=>y
    //由於錯誤的值要讓後面訪問到,因此這裏也要跑出個錯誤,否則會在以後then的resolve中捕獲
    onRejected = typeof onRejected === 'function'?onRejected:err=>{ throw err ;}
    //聲明一個promise對象
    let promise2
    if(this.status === 'resolve'){
      //由於在.then以後又是一個promise對象,因此這裏確定要返回一個promise對象
      promise2 = new Promise((resolve,reject)=>{
        setTimeout(()=>{
          //由於穿透值的緣故,在默認的跑出一個error後,不能再用下一個的reject來接受,只能經過try,catch        
          try{
            //由於有的時候須要判斷then中的方法是否返回一個promise對象,因此須要判斷
            //若是返回值爲promise對象,則須要取出結果看成promise2的resolve結果
            //若是不是,直接做爲promise2的resolve結果
            let x = onFufilled(this.value)
            //抽離出一個公共方法來判斷他們是否爲promise對象
            resolvePromise(promise2,x,resolve,reject)
          }catch(e){
            reject(e)
          }
        },0)
      })
    }
    if(this.status === 'reject'){
      promise2 = new Promise((resolve,reject)=>{
        setTimeout(()=>{
          try{
            let x = onRejected(this.reason)
            resolvePromise(promise2,x,resolve,reject)
          }catch(e){
            reject(e)
          }
        },0)
      })
    }
    if(this.status === 'pendding'){
      promise2 = new Promise((resolve,reject)=>{
        this.onResolvedCallbacks.push(()=>{
          // to do....
          setTimeout(()=>{
            try{
              let x = onFufilled(this.value)
              resolvePromise(promise2,x,resolve,reject)
            }catch(e){
              reject(e)
            }
          },0)
        })
        this.onRejectedCallbacks.push(()=>{
          setTimeout(()=>{
            try{
              let x = onRejected(this.reason)
              resolvePromise(promise2,x,resolve,reject)
            }catch(e){
              reject(e)
            }
          })
        })
      })
    }
    return promise2
  }

複製代碼

1.返回Promise?:首先咱們要注意的一點是,then有返回值,then了以後還能在then,那就說明以前的then返回的必然是個Promise。 2.爲何外面要包一層setTimeout?:由於Promise自己是一個異步方法,屬於微任務一列,必須得在執行棧執行完了在去取他的值,因此全部的返回值都得包一層異步setTimeout。 3.爲何開頭有兩個判斷?:這就是以前想要解決的若是then函數中的參數不是函數,那麼咱們須要作處理。若是onFufilled不是函數,就須要自定義個函數用來返回以前resolve的值,若是onRejected不是函數,自定義個函數拋出異常。這裏會有個小坑,若是這裏不拋出異常,會在下一個then的onFufilled中拿到值。又由於這裏拋出了異常因此全部的onFufilled或者onRejected都須要try/catch,這也是Promise/A+的規範。固然本人以爲成功的回調不須要拋出異常也能夠,你們能夠仔細想一想。 4.resolvePromise是什麼?:這實際上是官方Promise/A+的需求。由於你的then能夠返回任何職,固然包括Promise對象,而若是是Promise對象,咱們就須要將他拆解,直到它不是一個Promise對象,取其中的值。

function resolvePromise(promise2,x,resolve,reject){
  //判斷x和promise2之間的關係
  //由於promise2是上一個promise.then後的返回結果,因此若是相同,會致使下面的.then會是同一個promise2,一直都是,沒有盡頭
  if(x === promise2){//至關於promise.then以後return了本身,由於then會等待return後的promise,致使本身等待本身,一直處於等待
    return reject(new TypeError('循環引用'))
  }
  //若是x不是null,是對象或者方法
  if(x !== null && (typeof x === 'object' || typeof x === 'function')){
    //爲了判斷resolve過的就不用再reject了,(好比有reject和resolve的時候)
    let called
    try{//防止then出現異常,Object.defineProperty
      let then = x.then//取x的then方法可能會取到{then:{}},並無執行
      if(typeof then === 'function'){
        //咱們就認爲他是promise,call他,由於then方法中的this來自本身的promise對象
        then.call(x,y=>{//第一個參數是將x這個promise方法做爲this指向,後兩個參數分別爲成功失敗回調
          if(called) return;
          called = true
          //由於可能promise中還有promise,因此須要遞歸
          resolvePromise(promise2,y,resolve,reject)
        },err=>{
          if(called) return;
          called = true
          //一次錯誤就直接返回
          reject(err)
        })
      }else{
        //若是是個普通對象就直接返回resolve做爲結果
        resolve(x)
      }
    }catch(e){
      if(called) return;
      called = true
      reject(e)
    }
  }else{
    //這裏返回的是非函數,非對象的值,就直接放在promise2的resolve中做爲結果
    resolve(x)
  }
}

複製代碼

1.參數promise2(then函數返回的Promise對象),x(onFufilled函數的返回值),resolve、reject(最外層的Promise上的resolve和reject)。

2.爲何要在一開始判斷promise2和x?:首先在Promise/A+中寫了須要判斷這二者若是相等,須要拋出異常,我就來解釋一下爲何,若是這二者相等,咱們能夠看下下面的例子,第一次p2是p1.then出來的結果是個Promise對象,這個Promise對象在被建立的時候調用了resolvePromise(promise2,x,resolve,reject)函數,又由於x等於其自己,是個Promise,就須要then方法遞歸它,直到他不是Promise對象,可是x(p2)的結果還在等待,他卻想執行本身的then方法,就會致使等待。

let x1 = new Promise((resolve,reject)=>{
  resolve()
})

let x2 = x1.then(d=>{
    return p2
})

複製代碼

3.called是用來幹嗎的?:called變量主要是用來判斷若是resolvePromise函數已經resolve或者reject了,那就不須要在執行下面的resolce或者reject。

4.爲何取then這個屬性?:由於咱們須要去判斷x是否爲Promise,then屬性若是爲普通值,就直接resolve掉,若是是個function,就是Promise對象,以後咱們就須要將這個x的then方法進行執行,用call的緣由是由於then方法裏面this指向的問題。

5.爲何要遞歸去調用resolvePromise函數?:相信細心的人已經發現了,我這裏使用了遞歸調用法,首先這是Promise/A+中要求的,其次是業務場景的需求,當咱們碰到那種Promise的resolve裏的Promise的resolve裏又包了一個Promise的話,就須要遞歸取值,直到x不是Promise對象。

promise的all和race

  • all

all方法能夠說是Promise中很經常使用的方法了,它的做用就是將一個數組的Promise對象放在其中,當所有resolve的時候就會執行then方法,當有一個reject的時候就會執行catch,而且他們的結果也是按着數組中的順序來排放的,那麼咱們來實現一下。

//all方法(獲取全部的promise,都執行then,把結果放到數組,一塊兒返回)
Promise.all = function(promises){
  let arr = []
  let i = 0
  function processData(index,data){
    arr[index] = data
    i++
    if(i == promises.length){
      resolve(arr)
    }
  }
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(data=>{
        processData(i,data)
      },reject)
    }
  })
}

複製代碼
  • race race方法雖然不經常使用,可是在Promise方法中也是一個能用得上的方法,它的做用是將一個Promise數組放入race中,哪一個先執行完,race就直接執行完,並從then中取值。咱們來實現一下吧。
Promise.race = function(promises){
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(resolve,reject)
    }
  })
}
複製代碼

原理你們應該看懂了,很簡單,就是遍歷數組執行Promise,若是有一個Promise執行成功就resolve。

結尾

今天給你們分析promise的基本用法,以及promise的實現和用它解決什麼問題,但願你們能夠更好的理解和掌握promise,是本身的技術獲得進一步的提高!

相關文章
相關標籤/搜索