手把手系列。帶你手擼一個Promise

 

前提知識

高階函數

高階函數

知足如下條件之一:前端

  1. 函數的參數是一個 函數git

  2. 函數的返回值是一個函數github

閉包

定義: 有權訪問另外一個函數做用域中變量的函數(來源紅寶書)編程

函數柯里化

柯里化(Currying),把接受多個參數的函數轉換成接受一個單一參數的函數設計模式

舉例

let add = function(x{
        return function(y{
            return x + y
        }
    }

add(3)(4)       // 7

實際開發若是須要用到 柯里化,推薦使用 lodash.curry數組

應用(類型判斷)

typeof 沒法判斷 對象 類型promise

constructor 判斷 是誰構造出來的瀏覽器

instanceof 判斷 誰是誰的實例(即引用類型)微信

Object.prototype.toString.call() 完美判斷閉包

function isType(type{
  return function (content{
    return Object.prototype.toString.call(content) === `[object ${type}]`
  }
}
let isString = isType('String')
console.log(isString('132456'))
// 函數柯里化
console.log(isType('Number')(132456))

AOP 面向切片編程

也成爲 裝飾者模式

定義:指在不修改原有代碼的狀況下增長新功能

function sleep(who{
  who?console.log(who + '睡覺'):console.log('睡覺')

}
Function.prototype.before = function (callback{
  return (...args)=> {// args 傳入的參數數組
    callback()
    args?this(...args):this() // 傳入參數
  }
}
let Wash = sleep.before(function () {
  console.log('洗臉')
})
Wash()  // 洗臉 睡覺
Wash('我'// 洗臉 我睡覺

觀察者模式

訂閱發佈

有掙議,有些人說這個不算是觀察者模式。我的以爲算是屬於

let e = {
  _obg:{}, // 事件
  _callback:[], // 處理函數列表
  on(callback) { // 訂閱
    this._callback.push(callback)
  },
  emit(key,value){  // 發佈
    this._obg[key] = value
    this._callback.forEach(fn=>{
      fn(this._obg) // 參數傳入
    })
  }
}
e.on(function (obj{
  console.log('發佈一個')
  console.log(obj)
})

setTimeout(function () {
  e.emit('name','琛')
},1000)

舉一個簡單的例子,即微博,你關注了 A,A 發動態就會通知你。你和 A 沒有直接聯繫,經過 微博本身的調度來完成

觀察者模式

與發佈訂閱區別

發佈訂閱模式是 二者之間沒有直接關係,經過實踐調度中心來完成。而觀察者模式是相互依賴的,一個改變。另外一個也發生改變

例子

// 設計模式  觀察者模式

// 與發佈訂閱二者區別
//  發佈訂閱 是基於一箇中間管理  on 和 emit 沒有直接關係
//  觀察者模式 是 Observer and Observed 有直接關係
// 一個依賴改變,另外一個也改變 Vue
class Observer // 觀察者
  constructor(name) { // 傳遞參數
    this.name = name
  }
  update(baby){
    console.log(this.name+'知道'+baby.name+baby.state)
  }
}

class Observed{  // 被觀察者
  constructor(name) {
    this.name = name
    this.state = '開心'
    this.Observer = []
  }
  addObserver(o){
    this.Observer.push(o)
  }
  setState(state){
    this.state = state
    this.Observer.forEach(o=>{
      o.update(this)
    })
  }
}

let baby = new Observed('寶寶')
let dad = new Observer('爸爸')
let mom = new Observer('媽媽')
// 添加觀察者
baby.addObserver(dad)
baby.addObserver(mom)
// 設置狀態
baby.setState('不開心')
baby.setState('開心')

進入正題

基本的 promise 使用

promise

用來解決異步

Promise 是一個天生的類 須要傳入一個函數 默認會當即執行

有三種狀態 ,成功(resolve) ,失敗(reject), 等待

基本用法

let a = new Promise((resolve, reject) => {
    // 這兩個方法能夠更改promise 狀態
    // resolve()若是是這樣 下面的輸出爲 undefined
  resolve('成功')
    // reject('失敗')
})
a.then((data)=>{
  console.log(data) // 成功
},(err)=>{
  console.log(err) // 失敗
})

resolve,reject 這兩個方法能夠改變狀態。若是是 resolve,則走then的第一個函數,rejectthen的第二個函數。而且都將參數傳入。

**注意:**走成功了就不能夠走失敗,反之亦然

從 0 開始,手寫 Promise

明白了基本用法,咱們開始模仿一下Promise。這裏使用ES6語法來模仿。

這是 Promise 的規範

同步實現

首先,須要就收一個executor,是一個函數。當即執行,有三那種狀態,裏面還有resolve,reject兩個參數,這兩個參數是函數,須要接收兩個參數。同時promise還有then方法

思路分析

// promise 同步
// 三種狀態的定義
const Pending = 'PENDING'
const Success = 'SUCCESS'
const Error = 'Error'

// promise 的實現
class WritePromise {
  constructor(fn) { // 初始化

    this.state = Pending // 狀態初始化
    this.value = undefined // 成功信息
    this.err = undefined // 錯誤信息

    let resolve = (value)=>{
      if (this.state === Pending){
        this.value = value
        this.state = Success
      }
    }
    let reject = (err)=>{
      if (this.state === Pending){
        this.value = err
        this.state = Error
      }
    }
    //  可能會出錯
    try {
      fn(resolve,reject) // 當即執行
    }catch (e) {
      console.log(e) // 若是內部出錯 直接交給reject 方法向下傳遞
      reject(e)
    }
  }

  then(onfulfilled,onrejected){
    switch (this.state) {
      case Success:
        onfulfilled(this.value)
        break
      case Error:
        onrejected(this.err)
        break
    }
  }
}
// export default WritePromise  瀏覽器端
module.exports = WritePromise

在 new 的過程當中,執行constructor,傳入的 resolveorreject,進行賦值和狀態改變。而後then方法更具 state的狀態進行不一樣的操做。onfulfilled and onrejected是傳入的操做函數

爲何要加 try catch

在使用過程當中,不只僅只有reject能夠接收錯誤,也能夠手動拋出錯誤。這樣就reject捕獲不到錯誤。因此要加上 try catch 。保證能夠正常運行

測試

let WritePromise = require('./WritePromise')
let promise = new WritePromise((resolve, reject) => {
  // 1.
    resolve('成功')
    // 2.
    // reject('失敗')
})

promise.then((data) => { // onfulfilled 成功
  console.log(data)
}, (err) => { // onrejected 失敗
  console.log(err)
})

// 輸出  1.  成功   2. 失敗

異步實現

你們會發現。若是在resolveorreject,執行異步代碼(例如定時器)。會發現沒有結果。這是由於咱們剛纔寫的都是同步代碼。如今要改一下,改爲異步的

這時候就用到咱們前面的知識了,發佈訂閱模式

思路

首先,咱們應該知道在constructor中傳入的fn,若是加上定時器的話,它的狀態state不會發生任何改變。也就是一直處於等待狀態, 因此並不會執行then裏面的函數。因此咱們應該考慮一下當他處於等待的時候。是否是應該吧傳入的函數存儲起來,等到上面執行resolveorreject的時候,再把這個函數執行。

實現

// promise 異步
const Pending = 'PENDING'
const Success = 'SUCCESS'
const Error = 'Error'
class WritePromiseAsync {
  constructor(fn) {
    this.state = Pending
    this.value = undefined
    this.err = undefined
      //  回調函數的存儲
    this.SuccessCal = []
    this.ErrorCal = []

    let resolve = (value)=>{
      if (this.state === Pending){
        this.value = value
        this.state = Success
          // 對回調函數進行變量  而後執行
        this.SuccessCal.forEach((fn)=>fn())
      }
    }
    let reject = (err)=>{
      if (this.state === Pending){
        this.value = err
        this.state = Error
        this.ErrorCal.forEach((fn)=>fn())
      }
    }
    //  可能會出錯
    try {
      fn(resolve,reject) // 當即執行
    }catch (e) {
      console.log(e) // 若是內部出錯 直接交給reject 方法向下傳遞
      reject(e)
    }
  }
  then(onfulfilled,onrejected){
    switch (this.state) {
      case Success:
        onfulfilled(this.value)
        break
      case Error:
        onrejected(this.err)
        break
      case Pending:
        this.SuccessCal.push(()=>{
            // 爲何要這樣寫 由於這樣能夠作一些邏輯 AOP
          //  這裏面能夠作一些邏輯
          onfulfilled(this.value)
        })
        this.ErrorCal.push(()=>{
          onrejected(this.err)
        })
        break
    }
  }
}
module.exports = WritePromiseAsync

在順一遍。建立對象以後,調用then方法, 代碼開始執行,執行到then的時候,發現沒有對應的狀態改變,就先把它存儲起來。等到定時器結束以後,在把全部的函數都執行一次

鏈式調用

  • 每次調用返回的都是一個 新的 Promise 實例(這就是爲何能夠一直 then)

  • 鏈式調用的參數經過返回值傳遞

// 第二點的 代碼解釋

let b = new Promise((resolve, reject) => {
  resolve('data')
}).then((data)=>{
  data = data + '132456'
    // then能夠返回一個值,若是是普通值。就會走到下一個then 的成功中
  return data
}).then((data)=>{
  console.log(data) // 輸出 data132456
})

若是返回的不是普通值,是 promise,則會使用這個 promise 的結果

let b = new Promise((resolve, reject) => {
  resolve('data')
}).then((data)=>{
  data = data + '132456'
  return data
}).then(()=>{
  return new Promise((resolve, reject) => {
    resolve('我是promise 的返回')
      // 若是返回的是一個promise,那麼會採用這個promise的結果
  })
}).then((data)=>{
  console.log(data) // 輸出 我是promise 的返回
})

catch

用來捕獲 最近的且沒有捕獲的錯誤

let b = new Promise((resolve, reject) => {
  reject('data')
}).then().catch(err=>// 捕獲錯誤  捕獲最近的沒有捕獲的錯誤
      console.log(err+'catch'// datacatch
      // 注意 返回的也是undefined
    })

注意點

上述走的是成功,失敗也同樣。但會有一個小坑。

let b = new Promise((resolve, reject) => {
  resolve('data')
}).then(()=>{},err=>{
      console.log(err)
    // 在失敗函數中若是返回的是一個普通值,也會走下一次then的成功中
      // return undefined  至關於返回了一個這個
    }).then((data)=>{
      console.log(data+'success'// 這個會走 成功的值 輸出 underfinedsuccess
    },(err)=>{
      console.log(err+'err')
    })

特別注意,這裏會常常有遺漏。

鏈式調用的手寫實現

接着上次的WritePromiseAsync

實現屢次 then 傳遞 思路

原版作法中,當連續調用then方法的時候,會把上一次的結果傳遞給下一個then

上面說過每次調用then方法會返回一個promise實例。因此,咱們須要在調用then方法的時候返回一個promise的實例,而且接收到then方法的結果。在傳遞給這個promise

// 多餘的我就不寫了,主要寫差別化 的 then方法
 then(onfulfilled, onrejected) {
    let promise2 = new ChainPromise((resolve, reject) => {
      let x
      switch (this.state) {
        case Success:
          x = onfulfilled(this.value)
          resolve(x)
          break
        case Error:
          x = onrejected(this.value)
          reject(x)
          break
        case Pending:
          this.SuccessCal.push(() => {
            try {
              let x = onfulfilled(this.value)
              resolve(x)
            } catch (e) {
              reject(e)
            }
          })
          this.ErrorCal.push(() => {
            try {
              let x = onrejected(this.err)
              reject(x)
            } catch (e) {
              reject(e)
            }
          })
          break
      }
    })
    return promise2
  }

注意,調用的時候要把 then的兩個函數都要寫上,不然會報錯(尚未處理)

這樣事後 就能夠實現 屢次 then 方法傳遞結果了

實現 返回 promise 思路

說一下上面得哪一個x,咱們是直接把它返回給對應得處理方法,若是x是一個promise呢?按照原版得來講。咱們應該把這個promise的結果做爲返回值來繼續傳遞。因此咱們應該對這個x進行處理

建立一個方法solveX,來處理x

function solveX(promise2, x, resolve, reject{
  if (promise2 === x){
    return reject(new TypeError('引用自己'))
  }
  if ((typeof x === 'object' && x != null)|| typeof x === 'function'){
    // 處理promise
    try {
      let then = x.then
      if (typeof then === 'function'){ // 只能認定他是promise了
        then.call(x,(data)=>{
          console.log(data)
          resolve(data)
        },(err)=>{
          reject(err)
        })
      } else {
        resolve(x)
      }
    } catch (e) {
      reject(e) // 取值失敗 走err
    }
  }else {
    // 是一個普通值
    resolve(x)
  }
}

爲何要把promise2傳進來呢?由於若是 x就是promise2呢?則會是一個死循環。

x進行判斷,若是是普通值,直接返回就能夠了。若是不是,咱們取then方法(注意是方法,不是結果). 若是有這個方法,咱們就認定他是一個promise(可能有人會說若是then是一個空方法呢?,那也只能認定了,咱們最多隻能作到這種程度的判斷了。)

注意then的 this 指向問題

關於 promise2 傳入問題

try {
    x = onfulfilled(this.value)
    resolvePromise(promise2, x, resolve, reject)
    // 須要用x 來比較promise2的值
              // resolve()catch (e) { // 一旦出錯,走下一個promise 的錯誤處理方法
    reject(e)
}

若是直接傳入promise2的話,由於是同步的過程,在建立的時候promise2尚未生成,因此會報錯。這時候咱們能夠加一個定時器,把它變成異步。這就解決了這個問題

then(onfulfilled, onrejected) {
    let promise2 = new ChainPromise((resolve, reject) => {
      let x
      switch (this.state) {
        case Success:
          setTimeout(() => { // 若是不加定時器,promise2獲取不到
            try {
              x = onfulfilled(this.value)
              resolvePromise(promise2, x, resolve, reject) // 須要用x 來比較promise2的值
              // resolve()
            } catch (e) { // 一旦出錯,走下一個promise 的錯誤處理方法
              reject(e)
            }
          }, 0)
          //  實現以後要判斷 X  若是x是一個普通值,就正常返回。若是是一個promise 則把promise的執行結果做爲參數傳遞給 相應的處理函數
          break
        case Error:
          setTimeout(() => {
            try {
              x = onrejected(this.err)
              // reject(x)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
          break
        case Pending:
          this.SuccessCal.push(() => {
            try {
              let x = onfulfilled(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
          this.ErrorCal.push(() => {
            try {
              let x = onrejected(this.err)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
          break
      }
    })
    return promise2
  }

注意,即便寫的是0,也不會當即執行。

解決 then 裏面繼續返回 promise

上面咱們寫了一個方法來處理promise,只須要進行一個遞歸就能夠解決

/**  *  * @param promise2  * @param x  * @param resolve  * @param reject  * @returns {*}  */
function resolvePromise(promise2, x, resolve, reject{
  if (promise2 === x) {
    return reject(new TypeError('引用自己'))
  }
  if ((typeof x === 'object' && x != null) || typeof x === 'function') {
    // 處理promise
    try {
      let then = x.then
      if (typeof then === 'function') {
        then.call(x, (data) => {
          // resolve(data)  將data從新放入這個函數。直到是一個普通值再進行返回
          resolvePromise(promise2, data, resolve, reject)
        }, (err) => {
          // reject(err)
          resolvePromise(promise2, err, resolve, reject)
        })
      } else {
        resolve(x)
      }
    } catch (e) {
      reject(e) // 取值失敗 走err
    }
  } else {
    // 是一個普通值
    resolve(x)
  }
}

解決 then 必須傳值

then(onfulfilled, onrejected) {
    onfulfilled = typeof onfulfilled === 'function'?onfulfilled:v=>v
    onrejected = typeof onrejected === 'function'?onrejected:err=>{throw err}
    ........
}

將兩個函數進行判斷。若是不是函數,默認賦一個函數

防止屢次調用

function resolvePromise(promise2, x, resolve, reject{
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>--'))
  }
  let called  // 添加一個變量進行控制
  if ((typeof x === 'object' && x != null) || typeof x === 'function') {
    try {
      let then = x.then
      if (typeof then === 'function') {
        then.call(x, y => {
          if (called) return
          called = true
          resolvePromise(promise2, y, resolve, reject)
        }, r => {
          if (called) return // 若是發現被調用過 直接return
          called = true
          reject(r)
        })
      } else {
        resolve(x)
      }
    } catch (e) {
      if (called) return
      called = true
      reject(e)
    }
  } else {
    resolve(x)
  }
}

總結代碼

// promise 鏈式調用的實現
const PENDING  = 'PENDING'
const RESOLVED  = 'RESOLVED '
const REJECTED  = 'REJECTED'

function resolvePromise(promise2, x, resolve, reject{
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>--'))
  }
  let called
  if ((typeof x === 'object' && x != null) || typeof x === 'function') {
    try {
      let then = x.then
      if (typeof then === 'function') {
        then.call(x, y => {
          if (called) return
          called = true
          resolvePromise(promise2, y, resolve, reject)
        }, r => {
          if (called) return
          called = true
          reject(r)
        })
      } else {
        resolve(x)
      }
    } catch (e) {
      if (called) return
      called = true
      reject(e)
    }
  } else {
    resolve(x)
  }
}

class Promise {
  constructor(executor) {
    this.status = PENDING
    this.value = undefined
    this.reason = undefined
    this.SuccessCal = []
    this.ErrorCal = []

    let resolve = value => {
      if (this.status === PENDING) {
        this.value = value
        this.status = RESOLVED
        this.SuccessCal.forEach(fn => fn())
      }
    }
    let reject = reason => {
      if (this.status === PENDING) {
        this.reason = reason
        this.status = REJECTED
        this.ErrorCal.forEach(fn => fn())
      }
    }
    try {
      executor(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }

  then(onRESOLVED, onrejected) {
    onRESOLVED = typeof onRESOLVED === 'function' ? onRESOLVED : v => v
    onrejected = typeof onrejected === 'function' ? onrejected : err => {
      throw err
    }
    let promise2 = new Promise((resolve, reject) => {
      if (this.status === RESOLVED) {
        setTimeout(() => { // 若是不加定時器,promise2獲取不到
          try {
            let x = onRESOLVED(this.value)
            resolvePromise(promise2, x, resolve, reject) // 須要用x 來比較promise2的值
          } catch (e) { // 一旦出錯,走下一個promise 的錯誤處理方法
            reject(e)
          }
        }, 0)
        //  實現以後要判斷 X  若是x是一個普通值,就正常返回。若是是一個promise 則把promise的執行結果做爲參數傳遞給 相應的處理函數
      }
      if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = onrejected(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      }
      if (this.status === PENDING) {
        this.SuccessCal.push(() => { // 爲何要這樣寫 由於這樣能夠作一些邏輯
          //  這裏面能夠作一些邏輯
          setTimeout(() => {
            try {
              let x = onRESOLVED(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
        this.ErrorCal.push(() => {
          setTimeout(() => {
            try {
              let x = onrejected(this.reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
      }
    })
    return promise2
  }
}



// 測試 須要測試再添加
Promise.defer = Promise.deferred = function () {
  let dfd = {}
  dfd.promise = new Promise((resolve, reject) => {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}
module.exports = Promise

關於符合性測試

這個是測試工具的github

安裝以後, 執行

npx promises-aplus-tests promise.js

爲何是npx? 我沒有全局安裝

所有經過

測試結果.png

 

本文分享自微信公衆號 - 阿琛前端成長之路(lwkWyc)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索