前端學習筆記——promise實現

這個前端學習筆記是學習gitchat上的一個課程,這個課程的質量非常好,價格也不貴,非常時候前端入門的小夥伴們進階。
在這裏插入圖片描述
筆記不會涉及很多,主要是提取一些知識點,詳細的大家最好去過一遍教程,相信你一定會有很大的收穫

使用

我們看下微信小程序的請求API

wx.request({
  url: 'test.php', // 僅爲示例,並非真實的接口地址
  data: {
    x: '',
    y: ''
  },
  header: {
    'content-type': 'application/json' // 默認值
  },
  success(res) {
    console.log(res.data)
  }
})

如果需要通過請求結果再次請求,就會出現回調地獄

wx.request({
  url: 'test.php', // 僅爲示例,並非真實的接口地址
	//...
  success(res) {
    wx.request({
      url: 'test.php', // 僅爲示例,並非真實的接口地址
        //...
      success(res) {
        console.log(res.data)
      }
    })
  }
})

我們現在使用promise來改善這種寫法

const require = (url, data, method)=>{
    return new Promise((res,rej)=>{
        wx.request({
            url,
            data,
            method,
            head:{},
            success:(data)=>{
                res(data)
            },
            fail:(error)=>{
                rej(error)
            }
        })
    }})
}
// 多層請求嵌套
require('test.php', {data:'1'}, 'get').then((data)=>{
    return require('test.php', data, 'get')
}).then((data)=>{
    return require('test.php', data, 'get')
})// 可以繼續嵌套

從上面可知,promise 是一個構造函數,使用的是promise的實例。

接受一個函數參數,而這個函數的參數有2個,分別代表這成功回調和失敗回調

then接受的2個函數參數分別對應着上面的2個函數參數

function Promise(fn) {
	// fn(onfulfilled, onrejected){}
}

Promise.prototype.then = function(onfulfilled, onrejected) {

}

在promise構造函數的時候,通過onfulfilled, onrejected將任務時間放置到異步隊列,只要有一個觸發,另外一個就沒有用了,也是promise的唯一性,所以需要內部有2個值保存着這個函數。

promise還設置了一個狀態(pending,fulfilled,rejected)

function Promise(fn) {
  const self = this
  this.status = 'pending'
  this.value = null// 成功回調的值
  this.reason = null// 失敗回調的原因

  function resolve(value) {
    self.value = value
  }

  function reject(reason) {
    self.reason = reason
  }

  fn(resolve, reject)// 將then設置的函數回調傳給fn
}
// 爲了保證 onfulfilled、onrejected 能夠強健執行,我們爲其設置了默認值,其默認值爲一個函數元(Function.prototype)。
Promise.prototype.then = function(onfulfilled = Function.prototype, onrejected = Function.prototype) {
  onfulfilled(this.value)

  onrejected(this.reason)
}

狀態完善

從promise題目來看看狀態的作用

let promise = new Promise((resolve, reject) => {
  resolve('data')
  reject('error')
})

promise.then(data => {
  console.log(data)
}, error => {
  console.log(error)
})

只會輸出data,因爲只能執行一個回調,雖然函數裏調用了2個回調參數,但是promise只會執行第一個,這是因爲promise有一個狀態機制。

所以我們需要在內部加一個狀態,來控制回調的執行。

function Promise(fn) {
  this.status = 'pending'// 初始狀態
  this.value = null
  this.reason = null

  const resolve = value => {
    if (this.status === 'pending') {// 如果用戶執行了 resolve 則改變狀態爲fulfilled
      this.value = value
      this.status = 'fulfilled'
    }
  }

  const reject = reason => {
    if (this.status === 'pending') {// 如果用戶執行了 reject 則改變狀態爲rejected
      this.reason = reason
      this.status = 'rejected'
    }
  }

  fn(resolve, reject)
}
// 參數加多層判斷,如果傳入不是函數的話,將它轉換成函數
Promise.prototype.then = function(onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
  onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}

  if (this.status === 'fulfilled') {// 避免在加載的時候就執行成功回調
    onfulfilled(this.value)
  }
  if (this.status === 'rejected') {// 避免在加載的時候就執行失敗回調
    onrejected(this.reason)
  }
}

異步執行

從例子看看promise如何進行異步處理

let promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('data')
  }, 2000)
})

promise.then(data => {
  console.log(data)
})
// 2秒之後輸出data

由於我們2秒之後才調用resolve,所以promise2秒內狀態都沒有發生變化。雖然使用then,但是狀態一直是pending,所以無法執行onfulfilled,所以我們需要在內部中在promise改變狀態的時候,自己調用onfulfilled

function Promise(fn) {
  this.status = 'pending'
  this.value = null
  this.reason = null
  this.onFulfilledFunc = Function.prototype
  this.onRejectedFunc = Function.prototype

  const resolve = value => {
    if (this.status === 'pending') {
      this.value = value
      this.status = 'fulfilled'
		// 等待執行
      this.onFulfilledFunc(this.value)
    }

  }

  const reject = reason => {
    if (this.status === 'pending') {
      this.reason = reason
      this.status = 'rejected'
		// 等待執行
      this.onRejectedFunc(this.reason)
    }
  }

  fn(resolve, reject)
}

Promise.prototype.then = function(onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
  onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}

  if (this.status === 'fulfilled') {
    onfulfilled(this.value)
  }
  if (this.status === 'rejected') {
    onrejected(this.reason)
  }
  if (this.status === 'pending') {
      // 保存回調函數
    this.onFulfilledFunc = onfulfilled
    this.onRejectedFunc = onrejected
  }
}

但是如果不是異步調用resolve的話,就會導致執行順序和原本promise的不一樣

let promise = new Promise((resolve, reject) => {
   resolve('data')
})

promise.then(data => {
  console.log(data)
})
console.log(1)

正常會輸出 1然後輸出 data,但是我們實現的沒有考慮這個情況,輸出是data然後是1

所以我們在promise內部同樣需要進行一個異步處理。

我們要知道,promise異步是微任務的,可以使用nodejs 的nextTick或則mutationObserve來模擬,我們這裏使用setTimeout來實現。

const  resolve = value => {
  if (value instanceof Promise) {// 傳入的如果是promise 鏈模式
    return value.then(resolve, reject)
  }
  setTimeout(() => {
    if (this.status === 'pending') {
      this.value = value
      this.status = 'fulfilled'

      this.onFulfilledFunc(this.value)
    }
  })
}

const reject = reason => {
  setTimeout(() => {
    if (this.status === 'pending') {
      this.reason = reason
      this.status = 'rejected'

      this.onRejectedFunc(this.reason)
    }
  })
}

看一下到目前爲止的實現代碼:

function Promise(executor) {
  this.status = 'pending'
  this.value = null
  this.reason = null
  this.onFulfilledFunc = Function.prototype
  this.onRejectedFunc = Function.prototype

  const resolve = value => {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }
    setTimeout(() => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'fulfilled'

        this.onFulfilledFunc(this.value)
      }
    })
  }

  const reject = reason => {
    setTimeout(() => {
      if (this.status === 'pending') {
        this.reason = reason
        this.status = 'rejected'

        this.onRejectedFunc(this.reason)
      }
    })
  }

  executor(resolve, reject)
}

Promise.prototype.then = function(onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
  onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}

  if (this.status === 'fulfilled') {
    onfulfilled(this.value)
  }
  if (this.status === 'rejected') {
    onrejected(this.reason)
  }
  if (this.status === 'pending') {
    this.onFulfilledFunc = onfulfilled
    this.onRejectedFunc = onrejected
  }
}

這樣,promise的輸出就能夠異步進行了。

細節完善

因爲promise是支持鏈式添加then,所以我們需要返回一個promise作爲結果。

let promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('data')
  }, 2000)
})

promise.then(data => {
  console.log(`1: ${data}`)
})
promise.then(data => {
  console.log(`2: ${data}`)
})

輸出情況

//1: data
//2: data

這就需要將內部保存的onFulfilledFunc變成一個棧保存,使用數組即可。

function Promise(fn) {
  this.status = 'pending'
  this.value = null
  this.reason = null
  this.onFulfilledArray = []
  this.onRejectedArray = []

  const resolve = value => {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }
    setTimeout(() => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'fulfilled'

        this.onFulfilledArray.forEach(func => {// 按順序調用
          func(value)
        })
      }
    })
  }

  const reject = reason => {
    setTimeout(() => {
      if (this.status === 'pending') {
        this.reason = reason
        this.status = 'rejected'

        this.onRejectedArray.forEach(func => {// 按順序調用
          func(reason)
        })
      }
    })
  }

  fn(resolve, reject)
}

Promise.prototype.then = function(onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
  onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}

  if (this.status === 'fulfilled') {
    onfulfilled(this.value)
  }
  if (this.status === 'rejected') {
    onrejected(this.reason)
  }
  if (this.status === 'pending') {// 棧保存
    this.onFulfilledArray.push(onfulfilled)
    this.onRejectedArray.push(onrejected)
  }
}

還有一個細節,如果傳入的函數有語法錯誤,promise的狀態會變成rejected

我們只需要使用try-catch來執行函數,如果出錯就調用rejected

try {
  fn(resolve, reject)
} catch(e) {
  reject(e)
}

最終版本:

function Promise(executor) {
  this.status = 'pending'
  this.value = null
  this.reason = null
  this.onFulfilledArray = []
  this.onRejectedArray = []

  const resolve = value => {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }
    setTimeout(() => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'fulfilled'

        this.onFulfilledArray.forEach(func => {
          func(value)
        })
      }
    })
  }

  const reject = reason => {
    setTimeout(() => {
      if (this.status === 'pending') {
        this.reason = reason
        this.status = 'rejected'

        this.onRejectedArray.forEach(func => {
          func(reason)
        })
      }
    })
  }


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

Promise.prototype.then = function(onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
  onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error}

  if (this.status === 'fulfilled') {
    onfulfilled(this.value)
  }
  if (this.status === 'rejected') {
    onrejected(this.reason)
  }
  if (this.status === 'pending') {
    this.onFulfilledArray.push(onfulfilled)
    this.onRejectedArray.push(onrejected)
  }
}function Promise(executor) {
  this.status = 'pending'
  this.value = null
  this.reason = null
  this.onFulfilledArray = []
  this.onRejectedArray = []

  const resolve = value => {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }
    setTimeout(() => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'fulfilled'

        this.onFulfilledArray.forEach(func => {
          func(value)
        })
      }
    })
  }

  const reject = reason => {
    setTimeout(() => {
      if (this.status === 'pending') {
        this.reason = reason
        this.status = 'rejected'

        this.onRejectedArray.forEach(func => {
          func(reason)
        })
      }
    })
  }


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

Promise.prototype.then = function(onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
  onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error}

  if (this.status === 'fulfilled') {
    onfulfilled(this.value)
  }
  if (this.status === 'rejected') {
    onrejected(this.reason)
  }
  if (this.status === 'pending') {
    this.onFulfilledArray.push(onfulfilled)
    this.onRejectedArray.push(onrejected)
  }
}

鏈式調用

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('rex')
  }, 2000)
})

promise.then(data => {
  console.log(data)
  return `${data} next then`
})
.then(data => {
  console.log(data)
})

這段代碼執行後,將會在 2 秒後輸出:rex,緊接着輸出:rex next then。

初步實現

我們分解上一個例子,第一次綁定的then是

data => {
  console.log(data)
  return `${data} next then`
}

我們可以在then方法裏 添加一個返回新的promise

Promise.prototype.then = function(onfulfilled, onrejected) {
  // promise2 將作爲 then 方法的返回值
  let promise2
  if (this.status === 'fulfilled') {
    return promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    // 這個新的 promise2 resolved 的值爲 onfulfilled 的執行結果
                    let result = onfulfilled(this.value)
                    resolve(result)
                }
                catch(e) {
                    reject(e)
                }
            })
    })
  }
  if (this.status === 'rejected') {
    return promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    // 這個新的 promise2 reject 的值爲 onrejected 的執行結果
                    let result = onrejected(this.value)
                    resolve(result)
                }
                catch(e) {
                    reject(e)
                }
            })
    })
  }
  if (this.status === 'pending') {
    return promise2 = new Promise((resolve, reject) => {
      this.onFulfilledArray.push(() => {
        try {
          let result = onfulfilled(this.value)
          resolve(result)
        }
        catch(e) {
          reject(e)
        }
      })

      this.onRejectedArray.push(() => {
        try {
          let result = onrejected(this.reason)
          resolve(result)
        }
        catch(e) {
          reject(e)
        }
      })      
    })
  }
}

前面2中狀態都好理解,pendding這個狀態是如何讓promise能夠執行後續的then?

返回的新的promise2其實將promise2then執行函數保存在promise1的異步任務裏去了,當promise1執行結束的時候,promise2then開始執行,添加到異步任務去,這樣就能夠執行第二個then,同理promise2調用then會返回第三個的promise

繼續完善

如果用戶顯式返回promise

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('rex')
  }, 2000)
})

promise.then(data => {
  console.log(data)
  return new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(`${data} next then`)
    }, 4000)
  })
})
.then(data => {
  console.log(data)
})

我們需要將result返回的結果進行判斷,一種是普通值,一種是promise的實例

爲此我們抽象出 resolvePromise 方法進行統一處理。改動已有實現爲

const resolvePromise = (promise2, result, resolve, reject) => {

}

這個函數接受四個參數:

  • promise2: 返回的 Promise 實例
  • result: onfulfilled 或者 onrejected 函數的返回值
  • resolve: promise2 的 resolve 方法
  • reject: promise2 的 reject 方法
const resolvePromise = (promise2, result, resolve, reject) => {
  // 當 result 和 promise2 相等時,也就是說 onfulfilled 返回 promise2 時,進行 reject
    // 避免死循環
  if (result === promise2) {
    reject(new TypeError('error due to circular reference'))
  }

  // 是否已經執行過 onfulfilled 或者 onrejected
  let consumed = false
  let thenable

  if (result instanceof Promise) {
    if (result.status === 'pending') {
      result.then(function(data) {
        resolvePromise(promise2, data, resolve, reject)
      }, reject)
    } else {
      result.then(resolve, reject)
    }
    return
  }

  let isComplexResult = target => (typeof target === 'function' || typeof target === 'object') && (target !== null)

  // 如果返回的是疑似 Promise 類型
  if (isComplexResult(result)) {
    try {
      thenable = result.then
      // 如果返回的是 Promise 類型,具有 then 方法
      if (typeof thenable === 'function') {
        thenable.call(result, function(data) {
          if (consumed) {
            return
          }
          consumed = true

          return resolvePromise(promise2, data, resolve, reject)
        }, function(error) {
          if (consumed) {
            return
          }
          consumed = true

          return reject(error)
        })
      }
      else {
        resolve(result)
      }

    } catch(e) {
      if (consumed) {
        return
      }
      consumed = true
      return reject(e)
    }
  }
  else {
    resolve(result)
  }
}

對於onfulfilled函數返回的結果result:如果result 非 Promise 實例,非對象,非函數類型,是一個普通值的話(上述代碼中isComplexResult 函數進行判斷),我們直接將 promise2 以該值 resolve 掉。

對於onfulfilled 函數返回的結果result:如果result 含有then屬性方法,我們稱該屬性方法爲 thenable,說明result 是一個 Promise 實例,我們執行該實例的then 方法(既thenable),此時的返回結果有可能又是一個 Promise 實例類型,也可能是一個普通值,因此還要遞歸調用resolvePromise

從例子看爲什麼要遞歸調用

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('rex')
  }, 2000)
})

promise.then(data => {
  console.log(data)
  return new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(`${data} next then`)
    }, 4000)
  })
  .then(data => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
          resolve(`${data} next then`)
      }, 4000)
    })
  })
})
.then(data => {
  console.log(data)
})

該段代碼將會在 2 秒是輸出:rex,10 秒時輸出:rex next then next then。

promise 穿透

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('rex')
  }, 2000)
})


promise.then(null)
.then(data => {
  console.log(data)
})

2秒後輸出rex,這就是 Promise 穿透現象:

給 .then() 函數傳遞非函數值作爲其參數時,實際上會被解析成 .then(null),這時候的表現應該是:上一個 promise 對象的結果進行“穿透”,如果在後面鏈式調用仍存在第二個 .then() 函數時,將會獲取被穿透下來的結果。

其實很簡單,並且我們已經做到了。想想在 then() 方法的實現中:我們已經對 onfulfilled 和 onrejected 函數加上判斷:

Promise.prototype.then = function(onfulfilled = Function.prototype, onrejected = Function.prototype) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
  onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error }

    // ...
}

如果 onfulfilled 不是函數類型,則給一個默認值,該默認值是返回其參數的函數。onrejected 函數同理。這段邏輯,就是起到了實現“穿透”的作用。

promise靜態方法

  • Promise.prototype.catch
  • Promise.resolve,Promise.reject
  • Promise.all
  • Promise.race

Promise.prototype.catch 實現

Promise.prototype.catch 可以進行異常捕獲,它的典型用法:

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
      reject('rex error')
  }, 2000)
})

promise1.then(data => {
  console.log(data)
}).catch(error => {
  console.log(error)
})

會在 2 秒後輸出:rex error。

實現:

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

Promise.prototype.resolve實現

Promise.resolve(value) 方法返回一個以給定值解析後的 Promise 實例對象。

Promise.resolve('data').then(data => {
  console.log(data)
})
console.log(1)

先輸出 1 再輸出 data。

實現

Promise.resolve = function(value) {
  return new Promise((resolve, reject) => {
    resolve(value)
  })
}

Promise.all 實現

Promise.all(iterable) 方法返回一個 Promise 實例,此實例在 iterable 參數內所有的 promise 都“完成(resolved)”或參數中不包含 promise 時回調完成(resolve);如果參數中 promise 有一個失敗(rejected),此實例回調失敗(reject),失敗原因的是第一個失敗 promise 的結果。

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('rex')
  }, 2000)
})

const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('rex')
  }, 2000)
})

Promise.all([promise1, promise2]).then(data => {
  console.log(data)
})

將在 2 秒後輸出:[“rex”, “rex”]。

Promise.all = function(promiseArray) {
  if (!Array.isArray(promiseArray)) {
      throw new TypeError('The arguments should be an array!')
  }
  return new Promise((resolve, reject) => {
    try {
      let resultArray = []

      const length = promiseArray.length

      for (let i = 0; i <length; i++) {
        promiseArray[i].then(data => {
          resultArray.push(data)// 結果保存到數組
			// 全部異步執行完畢,返回最終結果
          if (resultArray.length === length) {
            resolve(resultArray)
          }
        }, reject)
      }
    }
    catch(e) {
      reject(e)
    }
  })
}

這裏用閉包保存一個length長度,沒成功執行一個promise就加1,這裏沒有加1,但是是使用result結果長度來判斷,如果長度一樣則代表所有異步已經全部返回,知道所有異步執行完成,就執行promise.all的異步任務,把所有結果當作resolve的參數

Promise.race 實現

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('lucas1')
  }, 2000)
})

const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('lucas2')
  }, 4000)
})

Promise.race([promise1, promise2]).then(data => {
  console.log(data)
})

將會在 2 秒後輸出:lucas1,實現 Promise.race 爲:

Promise.race = function(promiseArray) {
  if (!Array.isArray(promiseArray)) {
      throw new TypeError('The arguments should be an array!')
  }
  return new Promise((resolve, reject) => {
    try {
          const length = promiseArray.length
      for (let i = 0; i <length; i++) {
        promiseArray[i].then(resolve, reject)// 第一個執行成功的會調用resolve
      }
    }
    catch(e) {
      reject(e)
    }
  })
}

這裏使用 for 循環同步執行 promiseArray 數組中的所有 promise 實例 then 方法,第一個 resolve 的實例直接會觸發新 Promise(代碼中新 new 出來的) 實例的 resolve 方法。後續調用resolve就會失效了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章