手擼 Promise

手擼 Promise

  • Promise作爲ES6的核心內容,是前端童鞋必備的基礎知識!更是面試通關的必刷題!
  • Promise的出現,解決了"蠻荒時代"的回調地獄,讓js異步callback走向簡潔,優雅!
  • 本文參照Promise/A+實現(不足之處,請留言指出)

第一版(初出茅廬)

狀態
  • Promise有三種狀態值標識當前的異步狀態!
  • pending狀態可變爲fulfilled 或 rejected
  • 一旦處於fulfilled 或 rejected時 則不可改爲其他狀態
const PENDING = 'pending' // 待定,等待
const FULFILLED = 'fulfilled' // 成功
const REJECTED = 'rejected' // 失敗

promise的基本實現

  • promise構造函數、resolve方法、reject方法、then方法
// promise構造函數
function Promise(executor) {
  this.state = 'pending'
  this.value = null // 當狀態fulfilled,給定一個value值
  this.reason = null // 當狀態rejected,給定一個失敗原因
  
  // resolve方法
  function resolve(value) {
    if (this.status === PENDING) {
      this.status = FULFILLED
      this.value = value
    }
  }
  
  
  // reject方法
  function reject(reason) {
    if (this.status === PENDING) {
      this.status = REJECTED
      this.reason = reason
    }
  }

  try {
    executor(resolve, reject)
  } catch (e) {
    reject(e)
  }
}
  • then方法

當狀態爲fulfilled時調用onFulfilled, 當狀態爲rejected時調用onRejected


Promise.prototype.then =  function(onFulfilled, onRejected) {
    if(this.state === FULFILLED){
        onFulfilled(this.value)
    }
    if(this.state === FULFILLED){
        onFulfilled(this.reason)
    }
}

此時基本有了promise的樣子,但Promise真正要解決的異步問題,並未處理!

第二版 克敵(異步)制勝

  • 頭腦(漿糊)風暴,思考then方法是同步調用,那必然會在異步任務完成前執行,該如何處理呢?

此時次刻,聰明的你必然想到了訂閱發佈,先通過then訂閱異步成功後要執行的任務,在resolve 或 reject調用後執行已訂閱的任務

// 給Promise中添加兩個事件池,存儲訂閱事件

function Promise(executor) {
  ......
  this.onFulfilledCallBack = [] // onFulfilled訂閱池
  this.onRejectedCallBack = [] // onRejected訂閱池
  ......

// resolve方法、reject方法

  function resolve(value) {
    if (self.status === PENDING) {
      self.status = FULFILLED
      self.onFulfilledCallBack.forEach(cb => cb(self.reason))
    }
  }

  function reject(reason) {
    if (self.status === PENDING) {
      self.status = REJECTED
      self.reason = reason
      self.onRejectedCallBack.forEach(cb => cb(self.reason))
    }
  }

// 將onFulfilled, onRejected 放入事件池中

Promise.prototype.then =  function(onFulfilled, onRejected) {
    if(this.state === FULFILLED){
        onFulfilled(this.value)
    }
    if(this.state === FULFILLED){
        onFulfilled(this.reason)
    }
    if(this.state === PENDING){
        this.onFulfilledCallBack.push(onFulfilled)
        this.onRejectedCallBack.push(onRejected)
    }
}

此時此刻,恭喜你正式成爲一名江湖(江湖)劍客,可以行俠仗義了

帶三版 千秋萬代一統江湖-then方法的鏈式調用

  • 根據Promise/A+規範,then方法返回一個新的promise
Promise.prototype.then = function(onFulfilled, onRejected) {
    let promise2 = new Promise((resolve, reject) => {
      if (this.state === FULFILLED) {
        let x = onFulfilled(this.value);
        resolvePromise(promise2, x, resolve, reject);
      };
      if (this.state === REJECTED) {
        let x = onRejected(this.reason);
        resolvePromise(promise2, x, resolve, reject);
      };
      if (this.state === PENDING) {
        this.onFulfilledCallBack.push(()=>{
          let x = onFulfilled(this.value);
          resolvePromise(promise2, x, resolve, reject);
        })
        this.onRejectedCallBack.push(()=>{
          let x = onRejected(this.reason);
          resolvePromise(promise2, x, resolve, reject);
        })
      }
    })
    return promise2
}
  • 注意 resolvePromise 方法,我在調用事件池裏的方法後,爲什麼要resolvePromise方法將新的promise2與回調方法的返回值通過resolvePromise處理呢???
  • 思考:若事件池內的方法(then中用戶傳入的回調方法),返回的也是一個promise我們處理邏輯又將如何執行
  • 根據示例:觀察原生Promise
let test = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log(1)
    resolve(1)
  }, 1000)
})
test
  .then(data => {
    console.log('data:', data)
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log(2)
        resolve(2)
      }, 2000)
    }).then(data => {
      console.log('data:', data)
      console.log(3)
      return 3
    })
  })
  .then(data => {
    console.log('data:', data)
    console.log(4)
  })

// 輸出結果
1
data: 1
2
data: 2
3
data: 3
4
  • 據此我們可以發現外層的then回調會在內層的promise的then回調全部執行完纔會執行,那內外層的promise必然有着不可告人的祕密,他們就是通過resolvePromise方法“勾結”在一起
resolvePromise方法
  • 參數有promise2(then默認返回的promise)、x(then回調中return的對象)、resolve、reject

// 這裏先給出完整的 resolvePromise 代碼,看完後我們來思考幾個問題

function resolvePromise(promise2, x, resolve, reject){
  // 處理循環引用
  if(x === promise2){
    // reject報錯
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  // 防止多次調用
  let called;
  if (x instanceof Promise) {
    if (x.status === PENDING) {
      x.then(
        y => {
          resolvePromise(promise2, y, resolve, reject)
        },
        reason => {
          reject(reason)
        }
      )
    } else {
    
      x.then(resolve, reject)
    }
  } else if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      // A+規定,聲明then = x的then方法
      let then = x.then;
      // 如果then是函數,就默認是promise了
      if (typeof then === 'function') { 
        // 就讓then執行 第一個參數是this   後面是成功的回調 和 失敗的回調
        then.call(x, y => {
          // 成功和失敗只能調用一個
          if (called) return;
          called = true;
          // resolve的結果依舊是promise 那就繼續解析
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          // 成功和失敗只能調用一個
          if (called) return;
          called = true;
          reject(err);// 失敗了就失敗了
        })
      } else { 
           // 返回非promise,內層promise完成,執行promise2的resolve 迴歸到外城then的調用
        resolve(x); // 直接成功即可
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e); 
    }
  } else {
    resolve(x);
  }
}
  • 思考1: x === promise2,看下例子
let p1 = new Promise(resolve => {
  resolve(1);
});
var p2 = p1.then(data => {
  return p2;
})

如果不對x === promise2處理,會導致循環引用,自己等待自己

  • 思考2: else if (x != null && (typeof x === 'object' || typeof x === 'function'))

兼容符合 Promise/A+ 的 then 方法, 使實現方式不同(只要符合規範)的promise能夠相互調用。

第五版 內外兼修 處理異常

  • then中onFulfilled, onRejected均爲非必填參數
  • onFulfilled, onRejected均爲異步調用
  • onFulfilled, onRejected執行時用try catch捕獲異常然後reject(err)
Promise.prototype.then =  function(onFulfilled, onRejected) {

    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    
    // onRejected 若未傳,則通過throw err的方式將err拋出
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };

    let promise2 = new Promise((resolve, reject) => {
    
      if (this.state === FULFILLED) {
       setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0) 
      };
      
      if (this.state === REJECTED) {
           setTimeout(() => {
          // 如果報錯
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      
      if (this.state === PENDING) {
        this.onFulfilledCallBack.push(()=>{
             setTimeout(() => {
              try {
                let x = onFulfilled(this.value);
                resolvePromise(promise2, x, resolve, reject);
              } catch (e) {
                reject(e);
              }
            }, 0);
        })
        this.onRejectedCallBack.push(()=>{
             setTimeout(() => {
              try {
                let x = onRejected(this.reason);
                resolvePromise(promise2, x, resolve, reject);
              } catch (e) {
                reject(e);
              }
            }, 0);
        })
      }
    })
    
    return promise2
}
  • 到此你已完成了一個promise

完整代碼


// state 3種狀態
const PENDING = 'pending' // 待定
const FULFILLED = 'fulfilled' // 成功
const REJECTED = 'rejected' // 失敗

function Promise(executor) {
  this.state = PENDING
  this.value = undefined
  this.reason = undefined
  this.onFulfilledCallBack = []
  this.onRejectedCallBack = []
  let resolve = value => {
    if (this.state === 'pending') {
      this.state = 'fulfilled'
      this.value = value
      this.onFulfilledCallBack.forEach(fn => fn())
    }
  }
  let reject = reason => {
    if (this.state === 'pending') {
      this.state = 'rejected'
      this.reason = reason
      this.onRejectedCallBack.forEach(fn => fn())
    }
  }
  try {
    executor(resolve, reject)
  } catch (err) {
    reject(err)
  }
}

Promise.prototype.then = function(onFulfilled, onRejected) {
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value

  // onRejected 若未傳,則通過throw err的方式將err拋出
  onRejected =
    typeof onRejected === 'function'
      ? onRejected
      : err => {
          throw err
        }

  let promise2 = new Promise((resolve, reject) => {
    if (this.state === FULFILLED) {
      setTimeout(() => {
        try {
          let x = onFulfilled(this.value)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      }, 0)
    }
    if (this.state === REJECTED) {
      setTimeout(() => {
        // 如果報錯
        try {
          let x = onRejected(this.reason)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      }, 0)
    }
    if (this.state === PENDING) {
      this.onFulfilledCallBack.push(() => {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      })
      this.onRejectedCallBack.push(() => {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      })
    }
  })

  return promise2
}

function resolvePromise(promise2, x, resolve, reject) {
  // 處理循環引用
  if (x === promise2) {
    // reject報錯
    return reject(new TypeError('Chaining cycle detected for promise'))
  }
  // 防止多次調用
  let called
  if (x instanceof Promise) {
    if (x.status === PENDING) {
      x.then(
        y => {
          resolvePromise(promise2, y, resolve, reject)
        },
        reason => {
          reject(reason)
        }
      )
    } else {
      x.then(resolve, reject)
    }
  } else if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      // A+規定,聲明then = x的then方法
      let then = x.then
      // 如果then是函數,就默認是promise了
      if (typeof then === 'function') {
        // 就讓then執行 第一個參數是this   後面是成功的回調 和 失敗的回調
        then.call(
          x,
          y => {
            // 成功和失敗只能調用一個
            if (called) return
            called = true
            // resolve的結果依舊是promise 那就繼續解析
            resolvePromise(promise2, y, resolve, reject)
          },
          err => {
            // 成功和失敗只能調用一個
            if (called) return
            called = true
            reject(err) // 失敗了就失敗了
          }
        )
      } else {
        // 返回非promise,內層promise完成,執行promise2的resolve 迴歸到外城then的調用
        resolve(x) // 直接成功即可
      }
    } catch (e) {
      if (called) return
      called = true
      reject(e)
    }
  } else {
    resolve(x)
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章