ES6--淺析Promise內部結構

1、前言

什麼是promise?promsie的核心是什麼?promise如何解決回調地獄的?等問題git

一、什麼是promise?promise是表示異步操做的最終結果;能夠用來解決回調地獄和併發IO操做的問題github

A promise represents the eventual result of an asynchronous operation.web

二、promise 的核心是什麼?promise的核心就是鏈式調用數組

三、採用什麼方法能夠實現鏈式調用?經過使用then的方法,then方法是用來註冊在這個Promise狀態肯定後的回調,很明顯,then方法須要寫在原型鏈上。promise

四、promise是如何解決回調地獄的問題?(1)若是一個promise返回的是一個promise,會把這個promise傳遞結果傳遞到下一次的then中;(2)若是一個promise返回的是一個普通的值,會把這個普通值做爲下一次then的成功回調結果;(3)若是當前promise失敗了,會走下一個then的回調函數;(4)若是then不返回值,就會有一個默認值爲undefined,做爲普通值,會做爲下一個then的成功回調;(5)catch是錯誤沒有處理的狀況纔會執行;(6)then中能夠不寫東西瀏覽器

2、promise的標準解讀

一、只有一個then方法,沒有catch,race,all等方法,甚至沒有構造函數;併發

Promise標準中僅指定了Promise對象的then方法的行爲,其它一切咱們常見的方法/函數都並無指定,包括catch,race,all等經常使用方法,甚至也沒有指定該如何構造出一個Promise對象,另外then也沒有通常實現中(Q, $q等)所支持的第三個參數,通常稱onProgress框架

二、then方法返回一個新的Promise;異步

Promise的then方法返回一個新的Promise,而不是返回this,此處在下文會有更多解釋async

promise2 = promise1.then(alert)
promise2 != promise1 // true

三、不一樣Promise的實現須要能夠相互調用(interoperable)

四、Promise的初始狀態爲pending,它能夠由此狀態轉換爲fulfilled(本文爲了一致把此狀態叫作resolved)或者rejected,一旦狀態肯定,就不能夠再次轉換爲其它狀態,狀態肯定的過程稱爲settle

3、實現一個promise

一、構造函數

由於標準並無指定如何構造一個Promise對象,因此咱們一樣以目前通常Promise實現中通用的方法來構造一個Promise對象,也是ES6原生Promise裏所使用的方式,即:

// Promise構造函數接收一個executor函數,executor函數執行完同步或異步操做後,調用它的兩個參數resolve和reject
var promise = new Promise(function(resolve, reject) {
  /*
    若是操做成功,調用resolve並傳入value
    若是操做失敗,調用reject並傳入reason
  */
})

咱們先實現構造函數的框架以下:

function Promise(executor) {
  var self = this
  self.status = 'pending' // Promise當前的狀態
  self.data = undefined  // Promise的值
  self.onResolvedCallback = [] // Promise resolve時的回調函數集,由於在Promise結束以前有可能有多個回調添加到它上面
  self.onRejectedCallback = [] // Promise reject時的回調函數集,由於在Promise結束以前有可能有多個回調添加到它上面

  executor(resolve, reject) // 執行executor並傳入相應的參數
}

上面的代碼基本實現了Promise構造函數的主體,但目前還有兩個問題:

一、咱們給executor函數傳了兩個參數:resolve和reject,這兩個參數目前尚未定義

二、executor有可能會出錯(throw),相似下面這樣,而若是executor出錯,Promise應該被其throw出的值reject:

new Promise(function(resolve, reject) {
  throw 2
})

因此咱們須要在構造函數裏定義resolve和reject這兩個函數:

function Promise(executor) {
  var self = this
  self.status = 'pending' // Promise當前的狀態
  self.data = undefined  // Promise的值
  self.onResolvedCallback = [] // Promise resolve時的回調函數集,由於在Promise結束以前有可能有多個回調添加到它上面
  self.onRejectedCallback = [] // Promise reject時的回調函數集,由於在Promise結束以前有可能有多個回調添加到它上面

  function resolve(value) {
    // TODO
  }

  function reject(reason) {
    // TODO
  }

  try { // 考慮到執行executor的過程當中有可能出錯,因此咱們用try/catch塊給包起來,而且在出錯後以catch到的值reject掉這個Promise
    executor(resolve, reject) // 執行executor
  } catch(e) {
    reject(e)
  }
}

有人可能會問,resolve和reject這兩個函數能不能不定義在構造函數裏呢?考慮到咱們在executor函數裏是以resolve(value),reject(reason)的形式調用的這兩個函數,而不是以resolve.call(promise, value),reject.call(promise, reason)這種形式調用的,因此這兩個函數在調用時的內部也必然有一個隱含的this,也就是說,要麼這兩個函數是通過bind後傳給了executor,要麼它們定義在構造函數的內部,使用self來訪問所屬的Promise對象。因此若是咱們想把這兩個函數定義在構造函數的外部,確實是能夠這麼寫的:

function resolve() {
  // TODO
}
function reject() {
  // TODO
}
function Promise(executor) {
  try {
    executor(resolve.bind(this), reject.bind(this))
  } catch(e) {
    reject.bind(this)(e)
  }
}

可是衆所周知,bind也會返回一個新的函數,這麼一來仍是至關於每一個Promise對象都有一對屬於本身的resolve和reject函數,就跟寫在構造函數內部沒什麼區別了,因此咱們就直接把這兩個函數定義在構造函數裏面了。不過話說回來,若是瀏覽器對bind的所優化,使用後一種形式應該能夠提高一下內存使用效率。

另外咱們這裏的實現並無考慮隱藏this上的變量,這使得這個Promise的狀態能夠在executor函數外部被改變,在一個靠譜的實現裏,構造出的Promise對象的狀態和最終結果應當是沒法從外部更改的。

接下來,咱們實現resolve和reject這兩個函數

function Promise(executor) {
  // ...

  function resolve(value) {
    if (self.status === 'pending') {
      self.status = 'resolved'
      self.data = value
      for(var i = 0; i < self.onResolvedCallback.length; i++) {
        self.onResolvedCallback[i](value)
      }
    }
  }

  function reject(reason) {
    if (self.status === 'pending') {
      self.status = 'rejected'
      self.data = reason
      for(var i = 0; i < self.onRejectedCallback.length; i++) {
        self.onRejectedCallback[i](reason)
      }
    }
  }

  // ...
}

基本上就是在判斷狀態爲pending以後把狀態改成相應的值,並把對應的value和reason存在self的data屬性上面,以後執行相應的回調函數,邏輯很簡單,這裏就很少解釋了。

二、then方法

then方法是用來註冊這個promise肯定狀態後的回調,then方法是須要寫在原型鏈上。

天然約束:then方法會返回一個Promise,關於這一點,Promise/A+標準並無要求返回的這個Promise是一個新的對象,但在Promise/A標準中,明確規定了then要返回一個新的對象,目前的Promise實現中then幾乎都是返回一個新的Promise(https://promisesaplus.com/dif...,因此在咱們的實現中,也讓then返回一個新的Promise對象。

下面咱們來實現then方法:

// then方法接收兩個參數,onResolved,onRejected,分別爲Promise成功或失敗後的回調
Promise.prototype.then = function(onResolved, onRejected) {
  var self = this
  var promise2

  // 根據標準,若是then的參數不是function,則咱們須要忽略它,此處以以下方式處理
  onResolved = typeof onResolved === 'function' ? onResolved : function(v) {}
  onRejected = typeof onRejected === 'function' ? onRejected : function(r) {}

  if (self.status === 'resolved') {
    return promise2 = new Promise(function(resolve, reject) {

    })
  }

  if (self.status === 'rejected') {
    return promise2 = new Promise(function(resolve, reject) {

    })
  }

  if (self.status === 'pending') {
    return promise2 = new Promise(function(resolve, reject) {

    })
  }
}

Promise總共有三種可能的狀態,咱們分三個if塊來處理,在裏面分別都返回一個new Promise。

根據標準,咱們知道,對於以下代碼,promise2的值取決於then裏面函數的返回值:

promise2 = promise1.then(function(value) {
  return 4
}, function(reason) {
  throw new Error('sth went wrong')
})

若是promise1被resolve了,promise2的將被4 resolve,若是promise1被reject了,promise2將被new Error('sth went wrong') reject,更多複雜的狀況再也不詳述。

三、完整的promise

try {
  module.exports = Promise
} catch (e) {}

function Promise(executor) {
  var self = this

  self.status = 'pending'
  self.onResolvedCallback = []
  self.onRejectedCallback = []

  function resolve(value) {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }
    setTimeout(function() { // 異步執行全部的回調函數
      if (self.status === 'pending') {
        self.status = 'resolved'
        self.data = value
        for (var i = 0; i < self.onResolvedCallback.length; i++) {
          self.onResolvedCallback[i](value)
        }
      }
    })
  }

  function reject(reason) {
    setTimeout(function() { // 異步執行全部的回調函數
      if (self.status === 'pending') {
        self.status = 'rejected'
        self.data = reason
        for (var i = 0; i < self.onRejectedCallback.length; i++) {
          self.onRejectedCallback[i](reason)
        }
      }
    })
  }

  try {
    executor(resolve, reject)
  } catch (reason) {
    reject(reason)
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  var then
  var thenCalledOrThrow = false

  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise!'))
  }

  if (x instanceof Promise) {
    if (x.status === 'pending') { //because x could resolved by a Promise Object
      x.then(function(v) {
        resolvePromise(promise2, v, resolve, reject)
      }, reject)
    } else { //but if it is resolved, it will never resolved by a Promise Object but a static value;
      x.then(resolve, reject)
    }
    return
  }

  if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
    try {
      then = x.then //because x.then could be a getter
      if (typeof then === 'function') {
        then.call(x, function rs(y) {
          if (thenCalledOrThrow) return
          thenCalledOrThrow = true
          return resolvePromise(promise2, y, resolve, reject)
        }, function rj(r) {
          if (thenCalledOrThrow) return
          thenCalledOrThrow = true
          return reject(r)
        })
      } else {
        resolve(x)
      }
    } catch (e) {
      if (thenCalledOrThrow) return
      thenCalledOrThrow = true
      return reject(e)
    }
  } else {
    resolve(x)
  }
}

Promise.prototype.then = function(onResolved, onRejected) {
  var self = this
  var promise2
  onResolved = typeof onResolved === 'function' ? onResolved : function(v) {
    return v
  }
  onRejected = typeof onRejected === 'function' ? onRejected : function(r) {
    throw r
  }

  if (self.status === 'resolved') {
    return promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() { // 異步執行onResolved
        try {
          var x = onResolved(self.data)
          resolvePromise(promise2, x, resolve, reject)
        } catch (reason) {
          reject(reason)
        }
      })
    })
  }

  if (self.status === 'rejected') {
    return promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() { // 異步執行onRejected
        try {
          var x = onRejected(self.data)
          resolvePromise(promise2, x, resolve, reject)
        } catch (reason) {
          reject(reason)
        }
      })
    })
  }

  if (self.status === 'pending') {
    // 這裏之因此沒有異步執行,是由於這些函數必然會被resolve或reject調用,而resolve或reject函數裏的內容已經是異步執行,構造函數裏的定義
    return promise2 = new Promise(function(resolve, reject) {
      self.onResolvedCallback.push(function(value) {
        try {
          var x = onResolved(value)
          resolvePromise(promise2, x, resolve, reject)
        } catch (r) {
          reject(r)
        }
      })

      self.onRejectedCallback.push(function(reason) {
          try {
            var x = onRejected(reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (r) {
            reject(r)
          }
        })
    })
  }
}

Promise.prototype.catch = function(onRejected) {
  return this.then(null, onRejected)
}

Promise.deferred = Promise.defer = function() {
  var dfd = {}
  dfd.promise = new Promise(function(resolve, reject) {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}

4、promise的經常使用方法是如何實現

一、Promise.resolve / Promise.reject 實現

// 原生的Promise.resolve使用
Promise.resolve('hello swr').then((data)=>{ // 直接把成功的值傳遞給下一個then
    console.log(data) // hello swr
})

// 那麼Promise.resolve內部是怎麼實現的呢?
Promise.resolve = function(value){
    return new Promise((resolve,reject)=>{ // 在內部new一個Promise對象
       resolve(value) 
    })
}

// 同理,Promise.reject內部也是相似實現的
Promise.reject = function(reason){
    return new Promise((resolve,reject)=>{
        reject(reason)
    })
}

二、catch的實現

// 原生Promise的catch使用
Promise.reject('hello swr').catch((e)=>{
    console.log(e) // hello swr
})

// 上面這段代碼至關於下面這段代碼
Promise.reject('hello swr').then(null,(e)=>{ // then裏直接走了失敗的回調
    console.log(e) // hello swr
})

// 內部實現
Promise.prototype.catch = function(onRejected){
    return this.then(null,onRejected) // 至關於then裏的成功回調只傳個null
}

三、promise.all的實現同時執行多個異步,而且返回一個新的promise,成功的值是一個數組,該數組的成員的順序是傳參給promise.all的順序

// 原生Promise.all的使用
// 假設1.txt內容爲hello 2.txt內容爲swr
let fs = require('fs')
function read(filePath,encoding){
    return new Promise((resolve,reject)=>{ 
        fs.readFile(filePath,encoding,(err,data)=>{
            if(err) reject(err)
            resolve(data)
        })
    })
}

Promise.all([read('./1.txt','utf8'),read('./2.txt','utf8')]).then((data)=>{
    console.log(data) // 所有讀取成功後返回 ['hello','swr']
                      // 須要注意的是,當其中某個失敗的話,則會走失敗的回調函數
})

promise.all內部實現

Promise.all = function(promises){ // promises 是一個數組
    return new Promise((resolve,reject)=>{
        let arr = []
        let i = 0
        function processData(index,data){
            arr[index] = data
            // 5.咱們能用arr.length === promises.length來判斷請求是否所有完成嗎?
            // 答案是不行的,假設arr[2] = 'hello swr'
            // 那麼打印這個arr,將是[empty × 2, "hello swr"],
            // 此時數組長度也是爲3,而數組arr[0] arr[1]則爲空
            // 那麼換成如下的辦法
            if(++i === promises.length){ // 6.利用i自增來判斷是否都成功執行
                resolve(arr) // 此時arr 爲['hello','swr']
            }
        }
        
        for(let i = 0;i < promises.length;i++){ // 1.在此處遍歷執行
            promises[i].then((data)=>{ // 2.data是成功後返回的結果
                processData(i,data) // 4.由於Promise.all最終返回的是一個數組成員按照順序排序的數組
                                    // 並且異步執行,返回並不必定按照順序
                                    // 因此須要傳當前的i
            },reject) // 3.若是其中有一個失敗的話,則調用reject
        }
    })
}

四、promise.race方法實現,同時執行多個異步,而後那個快,就用那個的結果,race是賽跑

// 原生Promise.race的使用
// 一個成功就走成功的回調,一個失敗就走失敗的回調
Promise.race([read('./1.txt','utf8'),read('./2.txt','utf8')]).then((data)=>{
    console.log(data) // 可能返回 'hello' 也可能返回 'swr' 看哪一個返回快就用哪一個做爲結果
})

// 內部實現
Promise.race = function(promises){ // promises 是一個數組
    return new Promise((resolve,reject)=>{
        for(let i = 0;i < promises.length;i++){ 
            promises[i].then(resolve,reject) // 和上面Promise.all有點相似
        }
    })
}

五、promise.defer = promise.deferred這個語法糖怎麼理解呢?

這個語法糖能夠簡化一些操做,好比:

let fs = require('fs')
// 寫法一:
function read(filePath,encoding){
    // 這裏的new Promise依然是傳遞了一個executor回調函數
    // 咱們該怎樣減小回調函數嵌套呢?
    return new Promise((resolve,reject)=>{ 
        fs.readFile(filePath,encoding,(err,data)=>{
            if(err) reject(err)
            resolve(data)
        })
    })
}

// 寫法二:
// 這樣的寫法減小了一層回調函數的嵌套
function read(filePath,encoding){
    let dfd = Promise.defer()
    fs.readFile(filePath,encoding,(err,data)=>{
        if(err) dfd.reject(err)
        dfd.resolve(data)
    })
    return dfd.promise
}

read('./1.txt','utf8').then((data)=>{
    console.log(data)
})

5、promise的鏈式調用

promise的核心在於:鏈式調用。

promise主要解決兩個問題:

(1)回調地獄

(2)併發的異步IO操做,同一時間內把這個結果拿到,好比有兩個異步io操做,當這2個獲取完畢後,才執行相應的代碼

一、回調地獄怎麼解決

那麼咱們來看下面的代碼,而且改成promise。

// 回調函數
let fs = require('fs')
fs.readFile('./a.txt','utf8',(err,data)=>{ // 往fs.readFile方法傳遞了第三個爲函數的參數
    if(err){
        console.log(err)
        return
    }
    console.log(data)
})

// 改寫爲Promise
let fs = require('fs')
function read(filePath,encoding){
    return new Promise((resolve,reject)=>{
        fs.readFile(filePath,encoding,(err,data)=>{
            if(err) reject(err)
            resolve()
        })
    })
}

read('./a.txt','utf8').then((data)=>{ // 在這裏則再也不須要傳回調函數進去,而是採用then來達到鏈式調用
    console.log(data)
},(err)=>{
    console.log(err)
})
這樣看好像Promise也沒什麼優點,那麼接下來咱們對比一下

// 假設有3個文件
// - 1.txt    文本內容爲'2.txt'
// - 2.txt    文本內容爲'3.txt'
// - 3.txt    文本內容爲'hello swr'

// 用回調函數
fs.readFile('./1.txt','utf8',(err,data)=>{
    fs.readFile(data,'utf8',(err,data)=>{
        fs.readFile(data,'utf8',(err,data)=>{
            console.log(data) // hello swr
        })
    })
})

// 用Promise
read('./1.txt','utf8')
.then((data)=>{
    // 1.若是一個promise執行完後,返回的仍是一個promise,
    //   會把這個promise的執行結果會傳遞給下一次then中
    return read(data,'utf8')
})
.then((data)=>{
    return read(data,'utf8')
})
.then((data)=>{
    // 2.若是在then中返回的不是一個promise,
    //   而是一個普通值,會將這個普通值做爲下次then的成功的結果
    return data.split('').reverse().join('')
})
.then((data)=>{
    console.log(data) // rws olleh
    // 3.若是當前then中失敗了,會走下一個then的失敗回調
    throw new Error('出錯')
})
.then(null,(err)=>{
    console.log(err) // Error:出錯   報錯了
    // 4.若是在then中不返回值,雖然沒有顯式返回,
    //   可是默認是返回undefined,是屬於普通值,依然會把這個普通值傳到
    //   下一個then的成功回調中
})
.then((data)=>{
    console.log(data) // undefined
})

從上面能夠看得出,改寫爲Promise的代碼,更好閱讀和維護,從用Promise方式能夠得出結論:

(1)若是一個promise執行完後,返回的是一個promise,會將這個promise的執行結果傳遞給下一個then回調成功中;

(2)若是在then中返回的不是一個promise,而是一個普通的值,會將這個普通的值傳到下一個then成功回調中;

(3)若是當時then中失敗了,會走下一個then的回調失敗;

(4)若是then不返回值,可是默認是返回undefined的,屬於普通值,會將這個普通值傳到下一個then成功回調中。

若是在then中拋出錯誤,會怎麼樣呢?

狀況1:會被下一個then中的失敗回調捕獲

// 情景一,會被下一個then中的失敗回調捕獲
read('./1.txt','utf8')
.then((data)=>{
    throw new Error('出錯了')
})
.then(null,(err)=>{
    console.log(err) // Error:出錯了   報錯
})

狀況2:沒有被失敗回調捕獲,拋出錯誤最終會變成異常

read('./1.txt','utf8')
.then((data)=>{
    throw new Error('出錯了')
})

狀況3:若是沒有被失敗的回調捕獲,那麼最終會被catch捕獲到

read('./1.txt','utf8')
.then((data)=>{
    throw new Error('出錯了')
})
.then((data)=>{
    
})
.catch((err)=>{
    console.log(err) // Error:出錯了   報錯
})

狀況4:若是被失敗的回調捕獲,那麼就不會被catch捕獲到

read('./1.txt','utf8')
.then((data)=>{
    throw new Error('出錯了')
})
.then(null,(err)=>{
    console.log(err) // Error:出錯了   報錯
})
.catch((err)=>{
    console.log(err)  // 不會執行到這裏
})

(5)catch是錯誤沒有處理的狀況下才會執行

(6)then回調中能夠不寫

6、關於promise的其餘問題

一、性能問題

可能各位看官會以爲奇怪,Promise能有什麼性能問題呢?並無大量的計算啊,幾乎都是處理邏輯的代碼。

理論上說,不能叫作「性能問題」,而只是有可能出現的延遲問題。什麼意思呢,記得剛剛咱們說須要把4塊代碼包在setTimeout裏吧,先考慮以下代碼:

var start = +new Date()
function foo() {
  setTimeout(function() {
    console.log('setTimeout')
    if((+new Date) - start < 1000) {
      foo()
    }
  })
}
foo()

運行上面的代碼,會打印出多少次'setTimeout'呢,各位能夠本身試一下,不出意外的話,應該是250次左右,我剛剛運行了一次,是241次。這說明,上述代碼中兩次setTimeout運行的時間間隔約是4ms(另外,setInterval也是同樣的),實事上,這正是瀏覽器兩次Event Loop之間的時間間隔,相關標準各位能夠自行查閱。另外,在Node中,這個時間間隔跟瀏覽器不同,通過個人測試,是1ms。

單單一個4ms的延遲可能在通常的web應用中並不會有什麼問題,可是考慮極端狀況,咱們有20個Promise鏈式調用,加上代碼運行的時間,那麼這個鏈式調用的第一行代碼跟最後一行代碼的運行極可能會超過100ms,若是這之間沒有對UI有任何更新的話,雖然本質上沒有什麼性能問題,但可能會形成必定的卡頓或者閃爍,雖然在web應用中這種情形並不常見,可是在Node應用中,確實是有可能出現這樣的case的,因此一個可以應用於生產環境的實現有必要把這個延遲消除掉。在Node中,咱們能夠調用process.nextTick或者setImmediate(Q就是這麼作的),在瀏覽器中具體如何作,已經超出了本文的討論範圍,總的來講,就是咱們須要實現一個函數,行爲跟setTimeout同樣,但它須要異步且儘早的調用全部已經加入隊列的函數,這裏有一個實現。

二、如何中止一個promise鏈?

在一些場景裏,咱們會遇到一個較長的promise的鏈式調用,在某一步出現的錯誤讓咱們沒有必要去運行鏈式調用後面全部的代碼,相似於下面這樣的(此處省略then/catch裏的函數):

new Promise(function(resolve, reject) {
  resolve(42)
})
  .then(function(value) {
    // "Big ERROR!!!"
  })
  .catch()
  .then()
  .then()
  .catch()
  .then()

假設這個Big ERROR!!!的出現讓咱們徹底沒有必要運行後面全部的代碼了,但鏈式調用的後面即有catch,也有then,不管咱們是return仍是throw,都不可避免的會進入某一個catchthen裏面,那有沒有辦法讓這個鏈式調用在Big ERROR!!!的後面就停掉,徹底不去執行鏈式調用後面全部回調函數呢?

從一個實現者的角度看問題時,確實找到了答案,就是在發生Big ERROR後return一個Promise,但這個Promise的executor函數什麼也不作,這就意味着這個Promise將永遠處於pending狀態,因爲then返回的Promise會直接取這個永遠處於pending狀態的Promise的狀態,因而返回的這個Promise也將一直處於pending狀態,後面的代碼也就一直不會執行了,具體代碼以下:

new Promise(function(resolve, reject) {
  resolve(42)
})
  .then(function(value) {
    // "Big ERROR!!!"
    return new Promise(function(){})
  })
  .catch()
  .then()
  .then()
  .catch()
  .then()

這種方式看起來有些山寨,它也確實解決了問題。但它引入的一個新問題就是鏈式調用後面的全部回調函數都沒法被垃圾回收器回收(在一個靠譜的實現裏,Promise應該在執行完全部回調後刪除對全部回調函數的引用以讓它們能被回收,在前文的實現裏,爲了減小複雜度,並無作這種處理),但若是咱們不使用匿名函數,而是使用函數定義或者函數變量的話,在須要屢次執行的Promise鏈中,這些函數也都只有一份在內存中,不被回收也是能夠接受的。

將返回一個什麼也不作的Promise封裝成一個有語義的函數,以增長代碼的可讀性

Promise.cancel = Promise.stop = function() {
  return new Promise(function(){})
}
這麼使用了:

new Promise(function(resolve, reject) {
  resolve(42)
})
  .then(function(value) {
    // "Big ERROR!!!"
    return Promise.stop()
  })
  .catch()
  .then()
  .then()
  .catch()
  .then()

三、promise的鏈上返回的最後一個promise出錯了怎麼辦?

new Promise(function(resolve) {
  resolve(42)
})
  .then(function(value) {
    alter(value)
  })

但運行這段代碼的話你會發現什麼現象也不會發生,既不會alert出42,也不會在控制檯報錯,怎麼回事呢。細看最後一行,alert被打成了alter,那爲何控制檯也沒有報錯呢,由於alter所在的函數是被包在try/catch塊裏的,alter這個變量找不到就直接拋錯了,這個錯就正好成了then返回的Promise的rejection reason。

解決辦法:

(1)全部的promise鏈的最後都加上一個catch,這樣出錯後就會被捕獲到,這樣違背了DRY原則,並且繁瑣;

(2)借鑑Q的一個方法done,把這個方法加到promise鏈的最後,就可以處理捕獲最後一個promise出現的錯誤,其實個catch的思路同樣,這個是框架來實現的。

(3)在一個Promise被reject的時候檢查這個Promise的onRejectedCallback數組,若是它爲空,則說明它的錯誤將沒有函數處理,這個時候,咱們須要把錯誤輸出到控制檯,讓開發者能夠發現。

在Promise被reject但又沒有callback時,把錯誤輸出到控制檯。

四、出錯時,使用throw new Error()仍是使用return Promise.reject(new Error())呢?

從性能和編碼的溫馨角度考慮:

(1)性能方面:throw new Error()會使代碼進入catch塊裏的邏輯(咱們把多有的回調都包在try/catch裏),傳說throw多了會影響性能,由於一旦throw,代碼就有可能跳轉到不可預知的位置。

而使用promise.reject(new Error()),則須要構造一個新的promise對象(包含2個數組,4個函數:resolve/reject,onResolved/onRejected),也會花費必定的時間和內存。由於onResolved/onRejected函數是直接被包在promise實現裏的try裏,出錯後直接進入到這個try對應的catch塊,代碼的跳躍幅度相對較小,性能應該能夠忽略不記。

(2)編碼的溫馨度方面:出錯用throw,正經常使用return,正常能夠明顯的區分出錯和正常

綜上以爲仍是promise裏發現顯式錯誤後,用throw拋出錯誤比較好,而不是顯式的構造一個唄reject的promise對象。

7、實踐

注意:

一、不要把promise寫成嵌套的結構

// 錯誤的寫法
promise1.then(function(value) {
  promise1.then(function(value) {
    promise1.then(function(value) {

    })
  })
})

二、鏈式promise要返回一個promise,而不是構造一個promise

// 錯誤的寫法
Promise.resolve(1).then(function(){
  Promise.resolve(2)
}).then(function(){
  Promise.resolve(3)
})

8、參考

https://github.com/xieranmaya...

相關文章
相關標籤/搜索