欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

手把手教你實(shí)現(xiàn) Promise的使用方法

 更新時(shí)間:2020年09月02日 09:49:57   作者:Shenfq  
這篇文章主要介紹了手把手教你實(shí)現(xiàn) Promise的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

前言

很多 JavaScript 的初學(xué)者都曾感受過被回調(diào)地獄支配的恐懼,直至掌握了 Promise 語法才算解脫。雖然很多語言都早已內(nèi)置了 Promise ,但是 JavaScript 中真正將其發(fā)揚(yáng)光大的還是 jQuery 1.5 對(duì) $.ajax 的重構(gòu),支持了 Promise,而且用法也和 jQuery 推崇的鏈?zhǔn)秸{(diào)用不謀而合。后來 ES6 出世,大家才開始進(jìn)入全民 Promise 的時(shí)代,再后來 ES8 又引入了 async 語法,讓 JavaScript 的異步寫法更加優(yōu)雅。

今天我們就一步一步來實(shí)現(xiàn)一個(gè) Promise,如果你還沒有用過 Promise,建議先熟悉一下 Promise 語法再來閱讀本文。

構(gòu)造函數(shù)

在已有的 Promise/A+ 規(guī)范 中并沒有規(guī)定 promise 對(duì)象從何而來,在 jQuery 中通過調(diào)用 $.Deferred() 得到 promise 對(duì)象,ES6 中通過實(shí)例化 Promise 類得到 promise 對(duì)象。這里我們使用 ES 的語法,構(gòu)造一個(gè)類,通過實(shí)例化的方式返回 promise 對(duì)象,由于 Promise 已經(jīng)存在,我們暫時(shí)給這個(gè)類取名為 Deferred 。

class Deferred {
 constructor(callback) {
 const resolve = () => {
  // TODO
 }
 const reject = () => {
  // TODO
 }
 try {
  callback(resolve, reject)
 } catch (error) {
  reject(error)
 }
 }
}

構(gòu)造函數(shù)接受一個(gè) callback,調(diào)用 callback 的時(shí)候需傳入 resolve、reject 兩個(gè)方法。

Promise 的狀態(tài)

Promise 一共分為三個(gè)狀態(tài):

 pending :等待中,這是 Promise 的初始狀態(tài);

 

fulfilled :已結(jié)束,正常調(diào)用 resolve 的狀態(tài);

 

 rejected :已拒絕,內(nèi)部出現(xiàn)錯(cuò)誤,或者是調(diào)用 reject 之后的狀態(tài);

我們可以看到 Promise 在運(yùn)行期間有一個(gè)狀態(tài),存儲(chǔ)在 [[PromiseState]] 中。下面我們?yōu)?Deferred 添加一個(gè)狀態(tài)。

//基礎(chǔ)變量的定義
const STATUS = {
 PENDING: 'PENDING',
 FULFILLED: 'FULFILLED',
 REJECTED: 'REJECTED'
}

class Deferred {
 constructor(callback) {
 this.status = STATUS.PENDING

 const resolve = () => {
  // TODO
 }
 const reject = () => {
  // TODO
 }
 try {
  callback(resolve, reject)
 } catch (error) {
  // 出現(xiàn)異常直接進(jìn)行 reject
  reject(error)
 }
 }
}

這里還有個(gè)有意思的事情,早期瀏覽器的實(shí)現(xiàn)中 fulfilled 狀態(tài)是 resolved,明顯與 Promise 規(guī)范不符。當(dāng)然,現(xiàn)在已經(jīng)修復(fù)了。

內(nèi)部結(jié)果

除開狀態(tài),Promise 內(nèi)部還有個(gè)結(jié)果 [[PromiseResult]] ,用來暫存 resolve/reject 接受的值。

繼續(xù)在構(gòu)造函數(shù)中添加一個(gè)內(nèi)部結(jié)果。

class Deferred {
 constructor(callback) {
 this.value = undefined
 this.status = STATUS.PENDING

 const resolve = value => {
  this.value = value
  // TODO
 }
 const reject = reason => {
  this.value = reason
  // TODO
 }
 try {
  callback(resolve, reject)
 } catch (error) {
  // 出現(xiàn)異常直接進(jìn)行 reject
  reject(error)
 }
 }
}

儲(chǔ)存回調(diào)

使用 Promise 的時(shí)候,我們一般都會(huì)調(diào)用 promise 對(duì)象的 .then 方法,在 promise 狀態(tài)轉(zhuǎn)為 fulfilledrejected 的時(shí)候,拿到內(nèi)部結(jié)果,然后做后續(xù)的處理。所以構(gòu)造函數(shù)中,還需要構(gòu)造兩個(gè)數(shù)組,用來存儲(chǔ) .then 方法傳入的回調(diào)。

class Deferred {
 constructor(callback) {
 this.value = undefined
 this.status = STATUS.PENDING

 this.rejectQueue = []
 this.resolveQueue = []

 const resolve = value => {
  this.value = value
  // TODO
 }
 const reject = reason => {
  this.value = reason
  // TODO
 }
 try {
  callback(resolve, reject)
 } catch (error) {
  // 出現(xiàn)異常直接進(jìn)行 reject
  reject(error)
 }
 }
}

resolve 與 reject

 修改狀態(tài)

接下來,我們需要實(shí)現(xiàn) resolve 和 reject 兩個(gè)方法,這兩個(gè)方法在被調(diào)用的時(shí)候,會(huì)改變 promise 對(duì)象的狀態(tài)。而且任意一個(gè)方法在被調(diào)用之后,另外的方法是無法被調(diào)用的。

new Promise((resolve, reject) => {
 setTimeout(() => {
 resolve('🙆‍♂️')
 }, 500)
 setTimeout(() => {
 reject('🙅‍♂️')
 }, 800)
}).then(
 () => {
 console.log('fulfilled')
 },
 () => {
 console.log('rejected')
 }
)

此時(shí),控制臺(tái)只會(huì)打印出 fulfilled ,并不會(huì)出現(xiàn) rejected

class Deferred {
 constructor(callback) {
 this.value = undefined
 this.status = STATUS.PENDING

 this.rejectQueue = []
 this.resolveQueue = []

 let called // 用于判斷狀態(tài)是否被修改
 const resolve = value => {
   if (called) return
  called = true
  this.value = value
  // 修改狀態(tài)
  this.status = STATUS.FULFILLED
 }
 const reject = reason => {
   if (called) return
  called = true
  this.value = reason
  // 修改狀態(tài)
  this.status = STATUS.REJECTED
 }
 try {
  callback(resolve, reject)
 } catch (error) {
  // 出現(xiàn)異常直接進(jìn)行 reject
  reject(error)
 }
 }
}

調(diào)用回調(diào)

修改完?duì)顟B(tài)后,拿到結(jié)果的 promise 一般會(huì)調(diào)用 then 方法傳入的回調(diào)。

class Deferred {
 constructor(callback) {
 this.value = undefined
 this.status = STATUS.PENDING

 this.rejectQueue = []
 this.resolveQueue = []

 let called // 用于判斷狀態(tài)是否被修改
 const resolve = value => {
   if (called) return
  called = true
  this.value = value
  // 修改狀態(tài)
  this.status = STATUS.FULFILLED
  // 調(diào)用回調(diào)
  for (const fn of this.resolveQueue) {
  fn(this.value)
  }
 }
 const reject = reason => {
   if (called) return
  called = true
  this.value = reason
  // 修改狀態(tài)
  this.status = STATUS.REJECTED
  // 調(diào)用回調(diào)
  for (const fn of this.rejectQueue) {
  fn(this.value)
  }
 }
 try {
  callback(resolve, reject)
 } catch (error) {
  // 出現(xiàn)異常直接進(jìn)行 reject
  reject(error)
 }
 }
}

熟悉 JavaScript 事件系統(tǒng)的同學(xué)應(yīng)該知道, promise.then 方法中的回調(diào)會(huì)被放置到微任務(wù)隊(duì)列中,然后異步調(diào)用。

所以,我們需要將回調(diào)的調(diào)用放入異步隊(duì)列,這里我們可以放到 setTimeout 中進(jìn)行延遲調(diào)用,雖然不太符合規(guī)范,但是將就將就。

class Deferred {
 constructor(callback) {
 this.value = undefined
 this.status = STATUS.PENDING

 this.rejectQueue = []
 this.resolveQueue = []

 let called // 用于判斷狀態(tài)是否被修改
 const resolve = value => {
   if (called) return
  called = true
  // 異步調(diào)用
  setTimeout(() => {
   this.value = value
  // 修改狀態(tài)
  this.status = STATUS.FULFILLED
  // 調(diào)用回調(diào)
  for (const fn of this.resolveQueue) {
   fn(this.value)
  }
  })
 }
 const reject = reason => {
   if (called) return
  called = true
  // 異步調(diào)用
  setTimeout(() =>{
  this.value = reason
  // 修改狀態(tài)
  this.status = STATUS.REJECTED
  // 調(diào)用回調(diào)
  for (const fn of this.rejectQueue) {
   fn(this.value)
  }
  })
 }
 try {
  callback(resolve, reject)
 } catch (error) {
  // 出現(xiàn)異常直接進(jìn)行 reject
  reject(error)
 }
 }
}

then 方法

接下來我們需要實(shí)現(xiàn) then 方法,用過 Promise 的同學(xué)肯定知道,then 方法是能夠繼續(xù)進(jìn)行鏈?zhǔn)秸{(diào)用的,所以 then 必須要返回一個(gè) promise 對(duì)象。但是在 Promise/A+ 規(guī)范中,有明確的規(guī)定,then 方法返回的是一個(gè)新的 promise 對(duì)象,而不是直接返回 this,這一點(diǎn)我們可以通過下面代碼驗(yàn)證一下。

可以看到 p1 對(duì)象和 p2 是兩個(gè)不同的對(duì)象,并且 then 方法返回的 p2 對(duì)象也是 Promise 的實(shí)例。

除此之外,then 方法還需要判斷當(dāng)前狀態(tài),如果當(dāng)前狀態(tài)不是 pending 狀態(tài),則可以直接調(diào)用傳入的回調(diào),而不用再放入隊(duì)列進(jìn)行等待。

class Deferred {
 then(onResolve, onReject) {
 if (this.status === STATUS.PENDING) {
  // 將回調(diào)放入隊(duì)列中
  const rejectQueue = this.rejectQueue
  const resolveQueue = this.resolveQueue
  return new Deferred((resolve, reject) => {
  // 暫存到成功回調(diào)等待調(diào)用
  resolveQueue.push(function (innerValue) {
   try {
   const value = onResolve(innerValue)
   // 改變當(dāng)前 promise 的狀態(tài)
   resolve(value)
   } catch (error) {
   reject(error)
   }
  })
  // 暫存到失敗回調(diào)等待調(diào)用
  rejectQueue.push(function (innerValue) {
   try {
   const value = onReject(innerValue)
   // 改變當(dāng)前 promise 的狀態(tài)
   resolve(value)
   } catch (error) {
   reject(error)
   }
  })
  })
 } else {
  const innerValue = this.value
  const isFulfilled = this.status === STATUS.FULFILLED
  return new Deferred((resolve, reject) => {
  try {
   const value = isFulfilled
   ? onResolve(innerValue) // 成功狀態(tài)調(diào)用 onResolve
   : onReject(innerValue) // 失敗狀態(tài)調(diào)用 onReject
   resolve(value) // 返回結(jié)果給后面的 then
  } catch (error) {
   reject(error)
  }
  })
 }
 }
}

現(xiàn)在我們的邏輯已經(jīng)可以基本跑通,我們先試運(yùn)行一段代碼:

new Deferred(resolve => {
 setTimeout(() => {
 resolve(1)
 }, 3000)
}).then(val1 => {
 console.log('val1', val1)
 return val1 * 2
}).then(val2 => {
 console.log('val2', val2)
 return val2
})

3 秒后,控制臺(tái)出現(xiàn)如下結(jié)果:

可以看到,這基本符合我們的預(yù)期。

值穿透

如果我們?cè)谡{(diào)用 then 的時(shí)候,如果沒有傳入任何的參數(shù),按照規(guī)范,當(dāng)前 promise 的值是可以透?jìng)鞯较乱粋€(gè) then 方法的。例如,如下代碼:

new Deferred(resolve => {
 resolve(1)
})
 .then()
 .then()
 .then(val => {
 console.log(val)
 })

在控制臺(tái)并沒有看到任何輸出,而切換到 Promise 是可以看到正確結(jié)果的。

要解決這個(gè)方法很簡(jiǎn)單,只需要在 then 調(diào)用的時(shí)候判斷參數(shù)是否為一個(gè)函數(shù),如果不是則需要給一個(gè)默認(rèn)值。

const isFunction = fn => typeof fn === 'function'

class Deferred {
 then(onResolve, onReject) {
 // 解決值穿透
 onReject = isFunction(onReject) ? onReject : reason => { throw reason }
 onResolve = isFunction(onResolve) ? onResolve : value => { return value }
 if (this.status === STATUS.PENDING) {
  // ...
 } else {
  // ...
 }
 }
}

現(xiàn)在我們已經(jīng)可以拿到正確結(jié)果了。

一步之遙

現(xiàn)在我們距離完美實(shí)現(xiàn) then 方法只差一步之遙,那就是我們?cè)谡{(diào)用 then 方法傳入的 onResolve/onReject 回調(diào)時(shí),還需要判斷他們的返回值。如果回調(diào)的內(nèi)部返回的就是一個(gè) promise 對(duì)象,我們應(yīng)該如何處理?或者出現(xiàn)了循環(huán)引用,我們又該怎么處理?

前面我們?cè)谀玫?onResolve/onReject 的返回值后,直接就調(diào)用了 resolve 或者 resolve ,現(xiàn)在我們需要把他們的返回值進(jìn)行一些處理。

then(onResolve, onReject) {
 // 解決值穿透代碼已經(jīng)省略
 if (this.status === STATUS.PENDING) {
 // 將回調(diào)放入隊(duì)列中
 const rejectQueue = this.rejectQueue
 const resolveQueue = this.resolveQueue
 const promise = new Deferred((resolve, reject) => {
  // 暫存到成功回調(diào)等待調(diào)用
  resolveQueue.push(function (innerValue) {
  try {
   const value = onResolve(innerValue)
-   resolve(value)
+   doThenFunc(promise, value, resolve, reject)
  } catch (error) {
   reject(error)
  }
  })
  // 暫存到失敗回調(diào)等待調(diào)用
  rejectQueue.push(function (innerValue) {
  try {
   const value = onReject(innerValue)
-   resolve(value)
+   doThenFunc(promise, value, resolve, reject)
  } catch (error) {
   reject(error)
  }
  })
 })
 return promise
 } else {
 const innerValue = this.value
 const isFulfilled = this.status === STATUS.FULFILLED
 const promise = new Deferred((resolve, reject) => {
  try {
  const value = isFulfilled
  ? onResolve(innerValue) // 成功狀態(tài)調(diào)用 onResolve
  : onReject(innerValue) // 失敗狀態(tài)調(diào)用 onReject
-  resolve(value)
+  doThenFunc(promise, value, resolve, reject)
  } catch (error) {
  reject(error)
  }
 })
 return promise
 }
}

返回值判斷

在我們使用 Promise 的時(shí)候,經(jīng)常會(huì)在 then 方法中返回一個(gè)新的 Promise,然后把新的 Promise 完成后的內(nèi)部結(jié)果再傳遞給后面的 then 方法。

fetch('server/login')
 .then(user => {
  // 返回新的 promise 對(duì)象
  return fetch(`server/order/${user.id}`)
 })
 .then(order => {
  console.log(order)
 })
function doThenFunc(promise, value, resolve, reject) {
 // 如果 value 是 promise 對(duì)象
 if (value instanceof Deferred) {
 // 調(diào)用 then 方法,等待結(jié)果
 value.then(
  function (val) {
   doThenFunc(promise, value, resolve, reject)
  },
  function (reason) {
  reject(reason)
  }
 )
 return
 }
 // 如果非 promise 對(duì)象,則直接返回
 resolve(value)
}

判斷循環(huán)引用

如果當(dāng)前 then 方法回調(diào)函數(shù)返回值是當(dāng)前 then 方法產(chǎn)生的新的 promise 對(duì)象,則被認(rèn)為是循環(huán)引用,具體案例如下:

then 方法返回的新的 promise 對(duì)象 p1 ,在回調(diào)中被當(dāng)做返回值,此時(shí)會(huì)拋出一個(gè)異常。因?yàn)榘凑罩暗倪壿?,代碼將會(huì)一直困在這一段邏輯里。

所以,我們需要提前預(yù)防,及時(shí)拋出錯(cuò)誤。

function doThenFunc(promise, value, resolve, reject) {
 // 循環(huán)引用
 if (promise === value) {
 reject(
  new TypeError('Chaining cycle detected for promise')
 )
 return
 }
 // 如果 value 是 promise 對(duì)象
 if (value instanceof Deferred) {
 // 調(diào)用 then 方法,等待結(jié)果
 value.then(
  function (val) {
   doThenFunc(promise, value, resolve, reject)
  },
  function (reason) {
  reject(reason)
  }
 )
 return
 }
 // 如果非 promise 對(duì)象,則直接返回
 resolve(value)
}

現(xiàn)在我們?cè)僭囋囋?then 中返回一個(gè)新的 promise 對(duì)象。

const delayDouble = (num, time) => new Deferred((resolve) => {
 console.log(new Date())
 setTimeout(() => {
 resolve(2 * num)
 }, time)
})

new Deferred(resolve => {
 setTimeout(() => {
 resolve(1)
 }, 2000)
})
 .then(val => {
 console.log(new Date(), val)
 return delayDouble(val, 2000)
 })
 .then(val => {
 console.log(new Date(), val)
 })

上面的結(jié)果也是完美符合我們的預(yù)期。

catch 方法

catch 方法其實(shí)很簡(jiǎn)單,相當(dāng)于 then 方法的一個(gè)簡(jiǎn)寫。

class Deferred {
 constructor(callback) {}
 then(onResolve, onReject) {}
 catch(onReject) {
 return this.then(null, onReject)
 }
}

靜態(tài)方法

resolve/reject

Promise 類還提供了兩個(gè)靜態(tài)方法,直接返回狀態(tài)已經(jīng)固定的 promise 對(duì)象。

class Deferred {
 constructor(callback) {}
 then(onResolve, onReject) {}
 catch(onReject) {}
 
 static resolve(value) {
 return new Deferred((resolve, reject) => {
  resolve(value)
 })
 }

 static reject(reason) {
 return new Deferred((resolve, reject) => {
  reject(reason)
 })
 }
}

all

all 方法接受一個(gè) promise 對(duì)象的數(shù)組,等數(shù)組中所有的 promise 對(duì)象的狀態(tài)變?yōu)?fulfilled ,然后返回結(jié)果,其結(jié)果也是一個(gè)數(shù)組,數(shù)組的每個(gè)值對(duì)應(yīng)的是 promise 對(duì)象的內(nèi)部結(jié)果。

首先,我們需要先判斷傳入的參數(shù)是否為數(shù)組,然后構(gòu)造一個(gè)結(jié)果數(shù)組以及一個(gè)新的 promise 對(duì)象。

class Deferred {
 static all(promises) {
 // 非數(shù)組參數(shù),拋出異常
 if (!Array.isArray(promises)) {
  return Deferred.reject(new TypeError('args must be an array'))
 }

  // 用于存儲(chǔ)每個(gè) promise 對(duì)象的結(jié)果
 const result = []
 const length = promises.length
 // 如果 remaining 歸零,表示所有 promise 對(duì)象已經(jīng) fulfilled
 let remaining = length 
 const promise = new Deferred(function (resolve, reject) {
  // TODO
 })
  return promise
 }
}

接下來,我們需要進(jìn)行一下判斷,對(duì)每個(gè) promise 對(duì)象的 resolve 進(jìn)行攔截,每次 resolve 都需要將 remaining 減一,直到 remaining 歸零。

class Deferred {
 static all(promises) {
 // 非數(shù)組參數(shù),拋出異常
 if (!Array.isArray(promises)) {
  return Deferred.reject(new TypeError('args must be an array'))
 }

 const result = [] // 用于存儲(chǔ)每個(gè) promise 對(duì)象的結(jié)果
 const length = promises.length

 let remaining = length
 const promise = new Deferred(function (resolve, reject) {
  // 如果數(shù)組為空,則返回空結(jié)果
  if (promises.length === 0) return resolve(result)

  function done(index, value) {
  doThenFunc(
   promise,
   value,
   (val) => {
   // resolve 的結(jié)果放入 result 中
   result[index] = val
   if (--remaining === 0) {
    // 如果所有的 promise 都已經(jīng)返回結(jié)果
    // 然后運(yùn)行后面的邏輯
    resolve(result)
   }
   },
   reject
  )
  }
  // 放入異步隊(duì)列
  setTimeout(() => {
  for (let i = 0; i < length; i++) {
   done(i, promises[i])
  }
  })
 })
  return promise
 }
}

下面我們通過如下代碼,判斷邏輯是否正確。按照預(yù)期,代碼運(yùn)行后,在 3 秒之后,控制臺(tái)會(huì)打印一個(gè)數(shù)組 [2, 4, 6] 。

const delayDouble = (num, time) => new Deferred((resolve) => {
 setTimeout(() => {
 resolve(2 * num)
 }, time)
})

console.log(new Date())
Deferred.all([
 delayDouble(1, 1000),
 delayDouble(2, 2000),
 delayDouble(3, 3000)
]).then((results) => {
 console.log(new Date(), results)
})

上面的運(yùn)行結(jié)果,基本符合我們的預(yù)期。

race

race 方法同樣接受一個(gè) promise 對(duì)象的數(shù)組,但是它只需要有一個(gè) promise 變?yōu)?fulfilled 狀態(tài)就會(huì)返回結(jié)果。

class Deferred {
 static race(promises) {
 if (!Array.isArray(promises)) {
  return Deferred.reject(new TypeError('args must be an array'))
 }

 const length = promises.length
 const promise = new Deferred(function (resolve, reject) {
  if (promises.length === 0) return resolve([])

  function done(value) {
  doThenFunc(promise, value, resolve, reject)
  }

  // 放入異步隊(duì)列
  setTimeout(() => {
  for (let i = 0; i < length; i++) {
   done(promises[i])
  }
  })
 })
 return promise
 }
}

下面我們將前面驗(yàn)證 all 方法的案例改成 race。按照預(yù)期,代碼運(yùn)行后,在 1 秒之后,控制臺(tái)會(huì)打印一個(gè)2。

const delayDouble = (num, time) => new Deferred((resolve) => {
 setTimeout(() => {
 resolve(2 * num)
 }, time)
})

console.log(new Date())
Deferred.race([
 delayDouble(1, 1000),
 delayDouble(2, 2000),
 delayDouble(3, 3000)
]).then((results) => {
 console.log(new Date(), results)
})

上面的運(yùn)行結(jié)果,基本符合我們的預(yù)期。

總結(jié)

一個(gè)簡(jiǎn)易版的 Promise 類就已經(jīng)實(shí)現(xiàn)了,這里還是省略了部分細(xì)節(jié),完整代碼可以訪問 github 。Promise 的出現(xiàn)為后期的 async 語法打下了堅(jiān)實(shí)基礎(chǔ),下一篇博客可以好好聊一聊 JavaScript 的異步編程史,不小心又給自己挖坑了。。。

到此這篇關(guān)于手把手教你實(shí)現(xiàn) Promise的方法的文章就介紹到這了,更多相關(guān)Promise語法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論