小白都能看懂的promiseA+實現

DarK-AleX發表於2018-01-29

寫前端也有幾年了,最近在整理知識點,看了一遍promiseA+的規範,就想自己寫一遍這個前端非同步流程控制的經典函式。

先用個簡單的例子演示一下promise是怎麼工作的。

let myFirstPromise = new Promise(function(resolve, reject){
    //當非同步程式碼setTimeout執行成功時,我們才會呼叫resolve(...), 當非同步程式碼失敗時就會呼叫reject(...)
    //如果執成功回撥resolve,則永遠不會再走到reject
    //promise成功和失敗狀態是我們自己手動控制的
    //在本例中,我們使用setTimeout(...)來模擬非同步程式碼,實際編碼時可能是XHR請求或是HTML5的一些API方法.
    setTimeout(function(){
        resolve("成功!"); //程式碼正常執行!
    }, 250);
});
myFirstPromise.then(function(successMessage){
    //successMessage的值是上面呼叫resolve(...)方法傳入的值.
    //successMessage引數不一定非要是字串型別,這裡只是舉個例子
    console.log("Yay! " + successMessage);
});
複製程式碼

下面按promiseA+的規範自己實現一個promise

先寫一個建構函式 供外部呼叫 executor是帶有 resolve 和 reject 兩個引數的函式 。Promise建構函式執行時立即呼叫executor 函式, resolve 和 reject 兩個函式作為引數傳遞給executor。resolve 和 reject 函式被呼叫時,分別將promise的狀態改為fulfilled(完成)或rejected(失敗)。executor 內部通常會執行一些非同步操作,一旦完成,可以呼叫resolve函式來將promise狀態改成fulfilled,或者在發生錯誤時將它的狀態改為rejected。 如果在executor函式中丟擲一個錯誤,那麼該promise 狀態為rejected。executor函式的返回值被忽略。

var promise = new Promise(function(resolve, reject) {
  // 成功的話呼叫resolve並傳入value
  // 失敗的話呼叫reject並傳入reason
})
複製程式碼

第二步 寫建構函式的框架

function Promise(executor) {
  let self = this
  //由於promise非同步回撥方法,所以必須有等待狀態,成功狀態,失敗狀態。
  self.status = 'pending' // Promise當前的狀態
  self.data = undefined  // Promise的值
  self.onResolvedCallback = [] // Promise resolve時的回撥函式集,因為在Promise結束之前有可能有多個回撥新增到它上面
  self.onRejectedCallback = [] // Promise reject時的回撥函式集,因為在Promise結束之前有可能有多個回撥新增到它上面
  executor(resolve, reject) // 執行executor並傳入相應的引數
}
複製程式碼

第三步,實現resolve和reject和內部丟擲異常時立即將promise改為reject狀態

function Promise(executor) {
  let self = this
  self.status = 'pending' // Promise當前的狀態
  self.data = undefined  // Promise的值
  self.onResolvedCallback = [] // Promise resolve時的回撥函式集,因為在Promise結束之前有可能有多個回撥新增到它上面
  self.onRejectedCallback = [] // Promise reject時的回撥函式集,因為在Promise結束之前有可能有多個回撥新增到它上面

  function resolve(value) {
    //resolve方法,後邊會用到
    if (self.status === 'pending') {
      self.status = 'resolved'
      self.data = value
      //有可能呼叫多次resolve,把多個回撥存到成功陣列裡
      for(var i = 0; i < self.onResolvedCallback.length; i++) {
        self.onResolvedCallback[i](value)
      }
    }
  }

  function reject(reason) {
    // reject方法,後邊會用到
    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的過程出錯,所以我們用try/catch塊給包起來,並且在出錯後以catch到的值reject掉這個Promise
    executor(resolve, reject) // 執行executor
  } catch(e) {
  //出錯之後呼叫reject
    reject(e)
  }
}
複製程式碼

promise的then方法(解決洋蔥式的語法結構)

原生Promise物件有一個then方法,用來實現在這個Promise狀態確定後的回撥那麼這個then方法需要寫在原型鏈上。then方法會返回一個新的Promise,這樣就實現了鏈式呼叫,從而避免了之前callback回撥的那種洋蔥結構,使程式更簡單易懂。

Promise.prototype.then = function(onResolved, onRejected) {
  let self = this
  let promise2

  // 根據promiseA+,如果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) {

    })
  }
}
複製程式碼

因為then方法返回一個新的promise,所以我們需要在then裡面執行onResolved或者onRejected,並根據返回值來確定新的promise的結果,而且,如果onResolved/onRejected返回的是一個Promise,promise2將直接取這個Promise的結果:直接看程式碼

Promise.prototype.then = function(onResolved, onRejected) {
  var self = this
  var promise2
  
  onResolved = typeof onResolved === 'function' ? onResolved : function(value) {}
  onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {}

  if (self.status === 'resolved') {
    // 如果promise狀態已經確定並且是resolved,我們呼叫onResolved
    // 因為考慮到有可能丟擲異常,所以我們將其包在try/catch塊裡
    return promise2 = new Promise(function(resolve, reject) {
      try {
        var x = onResolved(self.data)
        if (x instanceof Promise) { // 如果onResolved的返回值是一個Promise物件,直接取它的結果做為promise2的結果,做到鏈式呼叫
          x.then(resolve, reject)
        }
        resolve(x) // 否則,以它的返回值做為promise2的結果
      } catch (e) {
        reject(e) // 如果出錯,以捕獲到的錯誤做為promise2的結果
      }
    })
  }

  // 此處邏輯與resolve相同
  if (self.status === 'rejected') {
    return promise2 = new Promise(function(resolve, reject) {
      try {
        var x = onRejected(self.data)
        if (x instanceof Promise) {
          x.then(resolve, reject)
        }
      } catch (e) {
        reject(e)
      }
    })
  }

  if (self.status === 'pending') {
  // 由於Promise還有pending狀態,我們並不能確定呼叫onResolved還是onRejected,需要確定promise的狀態才能進行下一步的處理
  // 邏輯與resolve一致
    return promise2 = new Promise(function(resolve, reject) {
      self.onResolvedCallback.push(function(value) {
        try {
          var x = onResolved(self.data)
          if (x instanceof Promise) {
            x.then(resolve, reject)
          }
        } catch (e) {
          reject(e)
        }
      })
      self.onRejectedCallback.push(function(reason) {
        try {
          var x = onRejected(self.data)
          if (x instanceof Promise) {
            x.then(resolve, reject)
          }
        } catch (e) {
          reject(e)
        }
      })
    })
  }
}
複製程式碼

這樣我我們就實現了一個簡單的promise,與官方promise基本一致,不過還有個問題,如果呼叫多個then,不過並沒有返回值,例如這樣

new Promise(resolve=>resolve(“呵呵呵”))
  .then()
  .then()
  .then(function foo(value) {
    alert(value)
  })
複製程式碼

只在最後一個then出現返回值,那麼就需要把promise的返回值直接傳到最後一個then中,這裡,我們做一個值的穿透,如果沒有返回值,那麼就把resolve向下拋。

onResolved = typeof onResolved === 'function' ? onResolved : function(value) {return value}
onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {throw reason}
複製程式碼

最後一點,在看promiseA+規範的時候發現then的回撥需要非同步執行,這裡沒有仔細思考過原因,在網上找過一些資料後覺得,promise非同步執行的原因主要是防止棧溢位和保持執行的一致性,promiseA+的標準給出的是要返回new Promise到jobQuene js的執行時裡對jobQuene和eventLoop的處理還是有一些差異的。不同之處在於 每個 JavaScript Runtime 可以有多個 Job Queue,但只有一個 Event Loop Queue當 JavaScript Engine 處理完當前事件佇列中的程式碼後,再執行本次任務中所有的 Job Queue,然後再處理 Event Loop Queue(下一次事件迴圈任務) 所以這裡為了模擬jobQuene 我們把promise執行回撥放在setTimeout中,保證回撥非同步執行,上程式碼

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) {
    // 非同步執行onRejected
      setTimeout(function() { 
        try {
          var x = onRejected(self.data)
          resolvePromise(promise2, x, resolve, reject)
        } catch (reason) {
          reject(reason)
        }
      })
    })
  }

  if (self.status === 'pending') {
    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的實現就已經寫完了,後面附上promise all和promise race的方法實現

all

var p1 = Promise.resolve(1),
    p2 = Promise.resolve(2),
    p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then(function (results) {
    console.log(results);  
});
// 結果[1, 2, 3]
複製程式碼

只有所有promise全都返回的時候,才執行resolve方法

Promise.all = function(arr){
    return new Promise((resolve,reject)=>{
        let list=[];
        arr.forEach((item)=>{
            item.then((data)=>{
                list.push(data);
                if(arr.length == list.length){
                    resolve(resolvList);
                }
            },(reason)=>{
                reject(reason);
            })
        })
    })
}
複製程式碼

race 只要有一個成功就立即返回。

var p1 = Promise.resolve(1),
    p2 = Promise.resolve(2),
    p3 = Promise.resolve(3);
Promise.race([p1, p2, p3]).then(function (value) {
    console.log(value);  // 1
});
//結果 1
複製程式碼
Promise.all = function(arr){
    return new Promise((resolve,reject)=>{
        let list=[];
        arr.forEach((item)=>{
            item.then((data)=>{
                resolve(resolvList);
            },(reason)=>{
                reject(reason);
            })
        })
    })
}
複製程式碼

相關文章