從0開始構建本身的前端知識體系-JS-跟着規範學Promise

前言

Promise做爲ES6極爲重要的一個特性,將咱們從無限的回調地獄中解脫出來,變爲鏈式的編寫回調,大大提升的代碼的可讀性。javascript

使用Promise是極爲簡單的,但只停留在會使用階段仍是會讓咱們不知不覺踩到一些坑的。本文會結合Promise/A+規範與示例來深刻學習Promisejava

本文較長,例子不少,末尾有一些應用 ^_^node

Promise

  • 兼容性

    Promise做爲是瀏覽器的內置函數對象,除IE不支持外全部主流版本的瀏覽器都是支持的,如不需支持IE能夠在不引入polyfill的狀況下放心食用git

  • 做用es6

    js是單線程的,異步是經過Event Loop來實現的,那麼須要一種更加友好的方式來實現咱們的異步操做,而不是無限的回調嵌套github

    // no promise
    callback(() => {
      callback(() => {
        callback(() => {
          ...
        })
      })
    })
    
    // with promise
    new Promise((resolve, reject) => {
    
    }).then(() => {
    
    }).then(() => {
    
    }).then(() => {
    
    })
  • APIapi

    先來簡單看一下Promise提供的一些API(注意區分靜態方法與實例方法)數組

    • 構造函數Promisepromise

      Promise是一個構造函數,能夠經過new操做符來建立一個Promise實例瀏覽器

      let promise = new Promise((resolve, reject) => {
        resolve(1)
      })
    • 靜態方法

      • Promise.resolve
      • Promise.reject
      • Promise.all
      • Promise.race
    • 實例方法

      • Promise.prototype.then
      • Promise.prototype.catch
  • 結合規範與樣例

    • 規範

      官方英文原版規範

      tips:

      1. 規範是規範,實現是實現,實現是按照規範來實現的,但不必定徹底等同於規範。
      2. 本文僅限瀏覽器環境測試,node環境可能會不一致
    • 狀態

      一個Promise實例只能處於pending,fulfilled,rejected三種狀態中的一種。每次建立的promise實例都會處於pending狀態,而且只能由pending變爲fulfilledreject狀態。一旦處於fulfilledrejected狀態不管進行什麼操做都不會再次變動狀態(規範2.1)

      // 建立一個處於pending狀態的promise實例
      let promise1 = new Promise((resolve, reject) => {
      
      })
      
      // 調用resolve()會使promise從pending狀態變爲fulfilled狀態
      let promise2 = new Promise((resolve, reject) => {
        resolve(1)
      })
      
      // 調用reject()會使promise從pending狀態變爲rejected狀態
      let promise3 = new Promise((resolve, reject) => {
        reject(1)
      })
      
      // 不管如何更改resolve與reject的執行順序,promise4始終只會處於先調用後轉換的狀態(狀態一旦變爲fulfilled或rejected後不會再次改變)
      let promise4 = new Promise((resolve, reject) => {
        resolve(1)
        reject(1)
      })
    • then

      • 參數

        實例方法then的回調函數接受兩個參數,onFulfilledonRejected,都爲可選參數(規範2.2.1)

        // onFulfilled會在狀態變爲fulfilled後調用,onFulfilled參數value爲resolve的第一個參數,onRejected同理。(規範2.2.2與2.2.3)
        
        let promise = new Promise((resolve, reject) => {
          setTimeout(() => {
            resolve(1)
          }, 1000) 
        })
        
        promise.then((value) => {
          console.log(value)  // 1秒後輸出1
        }, (reason) => {
        
        })
        
        // 可被同一個promise屢次調用(規範2.2.6)
        promise.then()
        
        promise.then((value) => {
          console.log(value) // 1秒後輸出1
        })
      • 返回值

        then方法必須返回一個promise實例(規範2.2.7)

        假定 promise2 = promise1.then(onFulfilled, onRejected)

        var a = new Promise((resolve, reject) => {
          resolve(1)
        })
        
        var b = new Promise((resolve, reject) => {
          reject(1)
        })
        
        // 若是```onFulfilled```或```onRejected```返回了一個value ```x```,運行promise解決程序(下一節), [[Resolve]](promise2, x) (規範2.2.7.1)
        var a1 = a.then(function onFulfilled(value) {
          return 1
        }, function onRejected (reason) {
        
        })
        
        var b1 = b.then(function onFulfilled(value) {
          
        }, function onRejected (reason) {
          return 1
        })
        
        // 若是```onFulfilled```或```onRejected```拋出了一個exception ```e```, ```promise2```必須以e做爲reason rejected (規範2.2.7.2)
        // 故下方a2 b2 都爲狀態是rejected的promise實例
        var a2 = a.then(function onFulfilled(value) {
          throw Error('test')
        }, function onRejected (reason) {
        
        })
        
        var b2 = b.then(function onFulfilled(value) {
          
        }, function onRejected (reason) {
          throw Error('test')
        })
        
        // 若是promise1處於fulfilled狀態而且onFulfilled不是一個函數,那麼promise2會以與promise1具備相同value和相同的狀態, 可是與promise1並非同一Promise實例;若爲rejected狀態以此類推
        var a3 = a.then()
        a3 === a // false
        var b3 = b.then()
        b3 === b // false
    • 解決程序resolve

      在規範中, promise解決程序是一個以promisevalue做爲輸入的抽象操做,咱們表示爲[[Resolve]](promise, x)

      能夠認爲在實現裏Promise.resolve()new Promise((resolve, reject) => {})中的resolve都爲解決程序。規範中x對應實現中resolve的第一個參數,規範中promise在實現中等價於當前promise

      能夠理解爲Promise.resolve(x)new Promise(resolve) => {resolve(x)}等價

      var c = new Promise((resolve, reject) => {
        resolve(1)
      })
      
      var d = new Promise((resolve, reject) => {
        reject(1)
      })
      
      var e = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve('5s後resolve')
        }, 5000)
      })
      
      // 在返回值章節咱們瞭解到,onFulfilled若是爲函數在不拋出錯誤的狀況下,會調用解決程序處理返回值x
      // 若是x是promise, 採納他的狀態(注意,但then的返回值並不與x相等)(規範2.3.2)
      var c1 = Promise.resolve().then(function onFulfilled() {
        return c
      })
      
      c1 === c // false
      
      // pending狀態下的,只要e狀態變爲,e1也會改變(規範2.3.2.1) 
      var e1 = Promise.resolve().then(function onFulfilled () {
        return e
      })
      
      var c2 = Promise.resolve()
      // 若是promise和x是相同的對象, 使用TypeError做爲reason reject promise(規範2.3.1)
      var c3 = c2.then(function onFulfilled () {
        return c3
      })

      概念:若是一個函數或者對象具備then屬性,那麼叫作thenable(例: { then: function(){} })

      // 若是x是thenable 但x.then不是一個函數或者對象,直接返回狀態爲fulfilled,value爲x的promise實例(規範2.3.3.4)
      var c4 = c1.then(function onFulfilled () {
        return {}
      })
      
      var c5 = c1.then(function onFulfilled () {
        return {
          then: 2
        }
      })
      
      // 若是x是thenable 而且x.then是一個函數,那麼就會調用這個then方法執行,而且兩個參數resolve與reject擁有改變返回的promise狀態的權利,會按解決程序處理,若是不調用兩個參數方法,則返回的promise爲pending狀態,值得一提的是,調用then方法的時候回以返回對象x做爲then的this綁定調用(規範 2.3.3.3)
      
      var c6 = c1.then(function onFulfilled () {
        return {
          test: 'test',
          then: function (resolve, reject) {
            console.log(this.test)
            resolve('thenable')
          }
        }
      })
      
      var c7 = c1.then(function onFulfilled () {
        function x () {}
        x.test = 'test'
        x.then = function (resolve, reject) {
          console.log(this.test)
          resolve('thenable')
        }
        return x
      })
      
      var c8 = c1.then(function onFulfilled () {
        return {
          test: 'test',
          then: function (resolve, reject) {
            console.log(this.test)
            reject('thenable')
          }
        }
      })
      
      // 返回pending狀態的promise
      var c9 = c1.then(function onFulfilled () {
        return {
          then: function () {}
        }
      })
      
      // 若是x不是對象也不是函數,則返回狀態爲fulfilled的promise,value爲x
      
      var c10 = c1.then(function onFulfilled () {
        return 'hehe'
      })

綜上能夠總結幾點

1.  ```new Promise, promise.then, Promise.resolve()```都會返回promise實例
2.  Promise狀態一旦改變不可再更改
3.  ```then```方法返回對象的不一樣會致使不一樣的結果,若是意外返回了thenable有可能會形成意想不到的結果

應用

  • 封裝一個異步請求

    var promiseXHR = new Promise((resolve, reject) => {
      var xhr = new XMLHttpRequest()
      xhr.open('GET', 'http://www.baidu.com', true)
      xhr.onreadystatechange = function () {
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
          resolve(xhr.response)
        } else {
          reject()
        }
      }
      xhr.send(data)
    })
  • 同時請求按序處理

    // 假設有5個章節的數據,咱們須要分別獲取並讀取到c中,利用promise和es6數組api咱們能夠寫出簡潔優雅的代碼完成這個需求
    
    var list = ['Chapter1', 'Chapter2', 'Chapter3', 'Chapter4', 'Chapter5']
    
    var getData = function (key) {
      return new Promise((resolve, reject) => {
        // 模擬一些返回時間不相同的異步操做
        setTimeout(() => {
          resolve(key)
        }, 4000 * Math.random())
      })
    }
    
    var c = ''
    
    list.map(i => getData(i))
        .reduce((accumulator, current) => {
          console.log(accumulator)
          return accumulator.then(() => {
            return current
          }).then(value => {
            c+= value
          })
        }, Promise.resolve(''))
  • catch

    明明在咱們平常工做中經常使用到的catch方法,爲何到如今還一點都沒有提到呢?

    由於catch方法就是then方法封裝出來的語法糖而已,由於若是隻想捕獲錯誤,還每次都要書寫then的第一個參數,真的是麻煩至極,如今咱們來寫一個本身的catch

    Promise.prototype.mycatch = function (cb) {
        return this.then(1, function onRejected (reason) {
          return cb(reason)
        })
      }

    // 用到了規範中若是onFulfilled不是一個函數,則忽略,並使用原先promise狀態與value

  • finally

    有的瀏覽器實現了finally,而有的並無,從需求上來說finally只不過是最終要執行一個函數,咱們須要把應該有的狀態或者異常都繼續傳遞,不受其影響。執行的函數與promise的value無任何關係

    Promise.prototype.myfinally = function (cb) {
      return this.then(function onFulfilled (value) {
        return Promise.resolve(cb()).then(() => value)
      }, function onRejected (reason) {
        return Promise.resolve(cb()).then(() => {
          throw reason
        })
      })
    }

後記

經過閱讀規範並寫demo進行驗證測試,對Promise的理解更加深刻了,也能更好的使用它了
若是喜歡能夠star一下,會不斷更新github地址

相關文章
相關標籤/搜索