由淺入深,從掌握Promise的基本使用到手寫Promise


由淺入深,從掌握Promise的基本使用到手寫Promise

前言

在ES6之前,對於一些異步任務的處理始終沒有很好的方案可以解決,處理異步的方案可謂是十分混亂,在業務需求下異步請求的套用,就形成了回調地獄,嚴重影響代碼的閱讀性。而Promise的出現,給我們統一了規范,解決了之前處理異步任務的許多痛點,並且它友好的使用方式,使之成為了JavaScript一大重點,同時也是面試的高頻問點,下面就一起來全面認識一下Promise吧。

1.什么是Promise?

如果我們想在一個異步請求之后,拿到請求的結果,在ES6之前我們可以怎么做呢?

比如,給定一個請求地址,希望拿到它請求成功或者失敗的結果:

  • 可以通過分別設置成功和失敗的兩個回調
  • 當請求成功后調用成功的回調,將成功的結果傳遞過去;
  • 當請求失敗后調用失敗的回調,將失敗的結果傳遞過去;
function request(url, successCb, failCb) {
  setTimeout(function() {
    if (url === '/aaa/bbb') { // 請求成功
      let res = [1, 2, 3]
      successCb(res)
    } else { // 請求失敗
      let err = 'err message'
      failCb(err)
    }
  })
}

// 調用方式,從回調中拿結果
request('/aaa/bbb', function(res) {
  console.log(res)
}, function(err) {
  console.log(err)
})

將上面的情況使用Promise來實現一下:

  • Promise是一個類,通過new調用,可以給予調用者一個承諾;
  • 通過new創建Promise對象時,需要傳入一個回調函數,這個回調函數稱之為executor,executor接收兩個參數resolve和reject;
  • 傳入的回調會被立即執行,當調用resolve函數時,會去執行Promise對象的then方法中傳入的成功回調;
  • 當調用reject函數時,會去執行Promise對象的then方法中傳入的失敗回調函數,並且請求后的結果可以通過參數傳遞過去;
function request(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url === '/aaa/bbb') {
        let res = [1, 2, 3]
        resolve(res) // 請求成功調用resolve
      } else {
        let err = 'err message'
        reject(err) // 請求失敗調用reject
      }
    })
  })
}

const p = request('/aaa/bbb')

p.then(res => {
  console.log(res) // 拿到resolve傳遞過來的值
}, err => {
  console.log(err) // 拿到reject傳遞過來的值
})

2.Promise的三種狀態

為什么Promise能夠將請求的結果准確的傳遞到then中的回調函數中,因為Promise其核心就用三種狀態來進行管控。

  • 待定狀態(pending):Promise的初始狀態;
  • 已兌現(resolved、fulfilled):操作成功,如執行resolve時就變為該狀態;
  • 已拒絕(rejected):操作失敗,如執行reject時就變為該狀態;

通過上面的案例,可以在瀏覽器中查看Promise分別在執行resolve和reject后的打印結果和Promise當時處於的狀態:

  • resolve和reject都沒執行:

  • 執行resolve,請求成功:

  • 執行reject,請求失敗:

注意:在后續的對Promise的講述過程中,都需要帶着Promise的狀態去理解。

3.executor

executor是在創建Promise是需要傳入的一個回調函數,這個回調函數會被立即執行,並且傳入兩個參數,分別就是resolve和reject。

new Promise((resolve, reject) => {
  console.log('我是executor中的代碼,我會被立即執行~')
})

通常我們會在executor中確定Promise的狀態,而且狀態一旦被確定下來,Promise的狀態就會被鎖死,即Promise的狀態一旦修改,就不能再次更改了

  • 當調用resolve,如果resolve傳入的值不是一個Promise(即傳入的值為一個普通值),Promise的狀態就會立即變成fulfilled;
  • 但是,如果在resolve后接着調用reject,是不會有任何的效果的,因為reject已經無法改變Promise的結果了;

4.resolve的參數

上面聊到了resolve需要傳入一個普通值,Promise的狀態才會被立即鎖定為fulfilled,那么如果傳遞的不是普通值呢?一般resolve傳遞以下三類值,會有不同的表現效果。

  • 傳值一:resolve傳入一個普通值或普通對象,那么這個值會作為then中第一個回調的參數;

    const p = new Promise((resolve, reject) => {
      resolve(123)
    })
    
    p.then(res => {
      console.log(res) // 123
    })
    
  • 傳值二:resolve傳入一個Promise,那么這個傳入的Promise會決定原來Promise的狀態;

    • 傳入的Promise調用的是resolve;

      const newP = new Promise((resolve, reject) => {
        resolve(123)
      })
      
      const p = new Promise((resolve, reject) => {
        resolve(newP)
      })
      
      p.then(res => {
        console.log(res) // 123
      }, err => {
        console.log(err)
      })
      
    • 傳入的Promise調用的是reject;

      const newP = new Promise((resolve, reject) => {
        reject('err message')
      })
      
      const p = new Promise((resolve, reject) => {
        resolve(newP)
      })
      
      p.then(res => {
        console.log(res)
      }, err => {
        console.log(err) // err message
      })
      
  • 傳值三:resolve傳入一個特殊對象,該對象中實現了then方法,那么Promise的狀態就是對象中then方法執行后的結果來決定的;

    • then中執行了resolve;

      const obj = {
        then: function(resolve, reject) {
          resolve(123)
        }
      }
      
      const p = new Promise((resolve, reject) => {
        resolve(obj)
      })
      
      p.then(res => {
        console.log(res) // 123
      }, err => {
        console.log(err)
      })
      
    • then中執行了reject;

      const obj = {
        then: function(resolve, reject) {
          reject('err message')
        }
      }
      
      const p = new Promise((resolve, reject) => {
        resolve(obj)
      })
      
      p.then(res => {
        console.log(res)
      }, err => {
        console.log(err) // err message
      })
      

5.Promise相關實例方法

Promise的實例方法,就是可以通過其實例對象進行調用的方法。

5.1.then方法

then方法是Promise實例對象上的一個方法:Promise.prototype.then

(1)then方法接收兩個參數

  • 狀態變成fulfilled的回調函數;
  • 狀態變成rejected的回調函數;
promise.then(res => {
  console.log('狀態變成fulfilled回調')
}, err => {
  console.log('狀態變成rejected回調')
})

(2)then方法多次調用

  • 一個Promise的then方法是可以被多次調用的,每次調用都可以傳入對應的fulfilled回調;
  • 當Promise的狀態變成fulfilled的時候,這些回調函數都會被執行;
  • 反之,當Promise的狀態變成rejected,所有then中傳入的rejected回調都會被執行;
const p = new Promise((resolve, reject) => {
  resolve('aaa')
})

p.then(res => {
  console.log(res) // aaa
})
p.then(res => {
  console.log(res) // aaa
})
p.then(res => {
  console.log(res) // aaa
})

(3)then方法中的返回值

then調用本身是有返回值的,並且它的返回值是一個Promise,所以then可以進行鏈式調用,但是then方法調用的返回值的狀態是什么呢?主要是由其返回值決定的。

  • 當then方法中的回調在執行時處於pending狀態;

  • 當then方法中的回調返回一個結果時處於fulfilled狀態,並且會將結果作為resolve的參數;

    • 返回一個普通的值:這個普通的值會被作為一個新Promise的resolve中的值

      p.then(res => {
        return 123
        // 相當於:
        /*
          return new Promise((resolve, reject) => {
            resolve(123)
          })
        */
      }).then(res => {
        console.log(res) // 123
      })
      
    • 返回一個實現了then方法的對象:

      p.then(res => {
        const obj = {
          then: function(resolve, reject) {
            resolve('abc')
          }
        }
        return obj
        // 相當於:
        /*
          return new Promise((resolve, reject) => {
            resolve(obj.then)
          })
        */
      }).then(res => {
        console.log(res) // abc
      })
      
    • 返回一個Promise:

      p.then(res => {
        const newP = new Promise((resolve, reject) => {
          resolve(123)
        })
        return newP
        // 相當於:
        /*
          const newP = new Promise((resolve, reject) => {
            resolve(123)
          })
          return new Promise((resolve, reject) => {
            resolve(newP)
          })
        */
      }).then(res => {
        console.log(res) // 123
      })
      
  • 當then方法執行時拋出一個異常,就處於rejected狀態,同樣,Promise的executor在執行的時候拋出異常,Promise對應的狀態也會變成rejected;

    const p = new Promise((resolve, reject) => {
      throw new Error('err message')
    })
    
    p.then(res => {
      console.log(res)
    }, err => {
      console.log(err) // Error: err message
      return new Error('then err message')
    }).then(res => {
      console.log(res)
    }, err => {
      console.log(err) // Error: then err message
    })
    

5.2.catch方法

catch方法是Promise實例對象上的一個方法:Promise.prototype.catch

(1)catch方法可多次調用

  • 一個Promise的catch方法也是可以被多次調用的,每次調用都可以傳入對應的reject回調;
  • 當Promise的狀態變成rejected的時候,這些回調就都會執行;
  • catch方法的效果和then方法的第二個回調函數差不多,可用於替代then方法的第二個回調;
const p = new Promise((resolve, reject) => {
  reject('err message')
})

p.catch(err => {
  console.log(err) // err message
})
p.catch(err => {
  console.log(err) // err message
})
p.catch(err => {
  console.log(err) // err message
})

(2)catch方法的返回值

  • catch方法的執行返回的也是一個Promise對象,使用catch后面可以繼續調用then方法或者catch方法;

  • 如果在catch后面調用then方法,會進入到then方法的fulfilled回調函數中,因為catch返回的Promise默認是fulfilled;

    p.catch(err => {
      return 'catch return value'
    }).then(res => {
      console.log(res) // catch return value
    })
    
  • 如果catch后續又調用了catch,那么可以拋出一個異常,就會進入后面的catch回調中;

    p.catch(err => {
      throw new Error('catch err message')
    }).catch(err => {
      console.log(err) // Error: catch err message
    })
    

(3)catch的作用

  • catch主要是用於捕獲異常的,當executor拋出異常是,可以通過catch進行處理;

  • 注意:當Promise的executor執行reject或者拋出異常,后續必須要有捕獲異常的處理,如下代碼,雖然都調用了then方法,接着后續又調用了catch方法,但是then和catch是兩次獨立的調用,兩次調用並沒有聯系,所以就被認定為沒有處理異常。

    const p = new Promise((resolve, reject) => {
      reject('err message')
    })
    
    p.then(res => {
      console.log(res)
    })
    
    p.catch(err => {
      console.log(err)
    })
    

  • 正確處理的方法為:

    // 方法一:
    p.then(res => {
      console.log(res)
    }).catch(err => {
      console.log(err)
    })
    
    // 方法二:
    p.then(res => {
      console.log(res)
    }, err => {
      console.log(err)
    })
    

5.3.finally方法

finally方法是Promise實例對象上的一個方法:Promise.prototype.finally

  • finally是在ES9中新增的,無論Promise的狀態變成fulfilled還是rejected,最終都會執行finally中的回調;
  • 注意finally是不接收參數的,因為它必定執行;
const p = new Promise((resolve, reject) => {
  resolve(123)
})

p.then(res => {
  console.log(res) // 123
}).catch(err => {
  console.log(err)
}).finally(() => {
  console.log('finally code') // finally code
})

6.Promise相關類方法

Promise的類方法,就是直接通過Promise進行調用。

6.1.resolve方法

resolve方法具體有什么用呢?當我們希望將一個值轉成Promise來使用,就可以通過直接調用resolve方法來實現,其效果就相當於在new一個Promise時在executor中執行了resolve方法。

resolve傳入的參數類型:

  • 參數為一個普通的值;

    const p = Promise.resolve('aaaa')
    // 相當於:
    /*
      const p = new Promise((resolve, reject) => {
        resolve('aaaa')
      })
    */
    console.log(p)
    

  • 參數為一個實現了then方法的對象;

    const p = Promise.resolve({
      then: function(resolve, reject) {
        resolve('aaaa')
      }
    })
    // 相當於:
    /*
      const p = new Promise((resolve, reject) => {
        resolve({
          then: function(resolve, reject) {
            resolve('aaaa')
          }
        })
      })
    */
    console.log(p)
    

  • 參數為一個Promise;

    const p = Promise.resolve(new Promise((resolve, reject) => {
      resolve('abc')
    }))
    // 相當於:
    /*
      const p = new Promise((resolve, reject) => {
        resolve(new Promise((resolve, reject) => {
          resolve('abc')
        }))
      })
    */
    console.log(p)
    

6.2.reject方法

reject方法和resolve的用法一致,只不過是將可以得到一個狀態為rejected的Promise對象,並且reject不過傳入的是什么參數,都會原封不動作為rejected狀態傳遞到catch中。

// 1.傳入普通值
const p1 = Promise.reject(123)
p1.then(res => {
  console.log(res)
}).catch(err => {
  console.log('err:', err)
})

// 2.傳入實現then方法對象
const p2 = Promise.reject({
  then: function(resolve, reject) {
    resolve('aaaa')
  }
})
p2.then(res => {
  console.log(res)
}).catch(err => {
  console.log('err:', err)
})

// 3.傳入Promise
const p3 = Promise.reject(new Promise((resolve, reject) => {
  resolve('aaaa')
 })
)
p3.then(res => {
  console.log(res)
}).catch(err => {
  console.log('err:', err)
})

6.3.all方法

all方法可以接收由多個Promise對象組成的數組(准確來說是可接收一個可迭代對象),all方法調用返回的Promise狀態,由所有Promise對象共同決定。

  • 當傳入的所有Promise對象的狀態都為fulfilled是,all方法返回的Promise狀態就為fulfilled,並且會將所有Promise對象的返回值組成一個數組

    const p1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(111)
      }, 1000)
    })
    const p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(222)
      }, 2000)
    })
    const p3 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(333)
      }, 3000)
    })
    
    Promise.all([p1, p2, p3]).then(res => {
      console.log('res:', res) // res: [ 111, 222, 333 ]
    }).catch(err => {
      console.log('err:', err)
    })
    
  • 當傳入的Promise有一個變成了rejected狀態,那么就會獲取第一個變成rejected狀態的返回值作為all方法返回的Promise狀態;

    const p1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(111)
      }, 1000)
    })
    const p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject('err message')
      }, 2000)
    })
    const p3 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(333)
      }, 3000)
    })
    
    Promise.all([p1, p2, p3]).then(res => {
      console.log('res:', res)
    }).catch(err => {
      console.log('err:', err) // err: err message
    })
    

6.4.allSettled方法

相比於all方法,allSettled方法不管傳入的Promise對象的狀態是fulfilled還是rejected,最終都會講結果返回,並且返回的結果是一個數組,數組中存放着每一個Promise對應的狀態status和對應的值value。

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(111)
  }, 1000)
})
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('err message')
  }, 2000)
})
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(333)
  }, 3000)
})

Promise.allSettled([p1, p2, p3]).then(res => {
  console.log('res:', res)
}).catch(err => {
  console.log('err:', err)
})

6.5.race方法

race翻譯為競爭,顧名思義哪一個Promise對象最先返回結果,就使用最先返回結果的Promise狀態。

一下代碼是p1最先有結果的,p1中執行的是resolve,所以返回的狀態為fulfilled:

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(111)
  }, 1000)
})
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('err message')
  }, 2000)
})
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(333)
  }, 3000)
})

Promise.race([p1, p2, p3]).then(res => {
  console.log('res:', res) // res: 111
}).catch(err => {
  console.log('err:', err)
})

6.6.any方法

any方法是ES12中新增的方法,與race是類似的,any方法會等到有一個fulfilled狀態的Promise,才會決定any調用返回新Promise的狀態(也就是說any一定會等到有一個Promise狀態為fullfilled)。

那么,如果所有的Promise對象的狀態都變為了rejected呢?最終就會報一個AggregateError錯誤,如果想拿到所有的rejected狀態的返回值,可以通過在捕獲異常回調參數中的errors獲取:

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('err message1')
  }, 1000)
})
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('err message2')
  }, 2000)
})
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('err message3')
  }, 3000)
})

Promise.any([p1, p2, p3]).then(res => {
  console.log('res:', res)
}).catch(err => {
  console.log(err)
  console.log(err.errors)
})

注意:any方法是ES12新增的,node版本過低的話是會報錯找不到any方法的,可以在瀏覽器中測試。

7.手寫Promise

掌握了以上Promise的用法,那么就一步步來實現一下Promise吧。

7.1.executor的實現

  • 創建一個類,這個類可接收一個executor函數;
  • executor函數需傳入兩個函數resolve和reject,並且executor是需要立即執行的;
  • 創建三個常量用於管理Promise的三種狀態;
  • 一旦Promise的狀態改變就不能再次被修改
  • 還需將傳入resolve和reject的參數值進行保存,便於后續then的使用;
// 定義Promise的三種狀態常量
const PENDING_STATUS = 'pending'
const FULFILLED_STATUS = 'fulfilled'
const REJECTED_STATUS = 'rejected'

class MyPromise {
  constructor(executor) {
    // 初始化Promise的狀態為pending
    this.promiseStatus = PENDING_STATUS
    // 初始化變量,用於保存resolve和reject傳入的參數值
    this.value = undefined
    this.reason = undefined

    // 1.定義executor需要傳入的resolve函數
    const resolve = (value) => {
      // 只有當Promise的狀態為pending,才能將狀態改變fulfilled
      if (this.promiseStatus === PENDING_STATUS) {
        this.promiseStatus = FULFILLED_STATUS
        this.value = value
        console.log('調用了resolve,狀態變成fulfilled啦~')
      }
    }

    // 2.定義executor需要傳入的reject函數
    const reject = (reason) => {
      // 只有當Promise的狀態為pending,才能將狀態改變為rejected
      if (this.promiseStatus === PENDING_STATUS) {
        this.promiseStatus = REJECTED_STATUS
        this.reason = reason
        console.log('調用了reject,狀態變成rejected啦~')
      }
    }

    // 3.將定義的兩個函數傳入executor並調用
    executor(resolve, reject)
  }
}

簡單測試一下:

// 先調用resolve
new MyPromise((resolve, reject) => {
  resolve()
  reject()
})
// 先調用reject
new MyPromise((resolve, reject) => {
  reject()
  resolve()
})

7.2.then方法的實現

(1)then基本實現

  • then方法接收兩個參數:
    • onFulfilled回調:當Promise狀態變為fulfilled需要執行的回調;
    • onRejected回調:當Promise狀態變為rejected需要執行的回調
class MyPromise {
  constructor(executor) {
    // 初始化Promise的狀態為pending
    this.promiseStatus = PENDING_STATUS
    // 初始化變量,用於保存resolve和reject傳入的參數值
    this.value = undefined
    this.reason = undefined

    // 1.定義executor需要傳入的resolve函數
    const resolve = (value) => {
      // 只有當Promise的狀態為pending,才能將狀態改變fulfilled
      if (this.promiseStatus === PENDING_STATUS) {
        // 添加微任務
        queueMicrotask(() => {
          // 如果Promise狀態不為pending,后面的代碼就不再執行了
          if (this.promiseStatus !== PENDING_STATUS) return
          this.promiseStatus = FULFILLED_STATUS
          this.value = value
          // 狀態變成fulfilled就去調用onFulfilled
          this.onFulfilled(this.value)
        })
      }
    }

    // 2.定義executor需要傳入的reject函數
    const reject = (reason) => {
      // 只有當Promise的狀態為pending,才能將狀態改變為rejected
      if (this.promiseStatus === PENDING_STATUS) {
        // 添加微任務
        queueMicrotask(() => {
          // 如果Promise狀態不為pending,后面的代碼就不再執行了
          if (this.promiseStatus !== PENDING_STATUS) return
          this.promiseStatus = REJECTED_STATUS
          this.reason = reason
          // 狀態變成rejected就去調用onRejected
          this.onRejected(this.reason)
        })
      }
    }

    // 3.將定義的兩個函數傳入executor並調用
    executor(resolve, reject)
  }

  then(onFulfilled, onRejected) {
    // 保存fulfilled和rejected狀態的回調
    this.onFulfilled = onFulfilled
    this.onRejected = onRejected
  }
}

注意:這里將onFulfilled和onRejected的調動放在了queueMicrotask,在JavaScript中可以通過queueMicrotask使用微任務,而原Promise的then中回調的執行,也是會被放在微任務中的,為什么要放在微任務中呢?

原因:如果不使用微任務,那么在executor中執行resolve或者reject時,then方法還沒被調用,onFulfilled和onRejected就都還沒被賦值,所以調用時會報錯,加入微任務就可以實現將onFulfilled和onRejected的調用推遲到下一次事件循環,也就是等then調用后賦值了才會執行。

簡單測試一下:

const p1 = new MyPromise((resolve, reject) => {
  resolve('aaaa')
})
const p2 = new MyPromise((resolve, reject) => {
  reject('err message')
})

p1.then(res => {
  console.log(res) // aaaa
}, err => {
  console.log(err)
})

p2.then(res => {
  console.log(res)
}, err => {
  console.log(err) // err message
})

(2)then優化一

  • 對於以上then的基本實現,還存在一些不足之處,比如:
    • then方法是可以進行多次調用的,並且每一次調用都是獨立調用,互不影響,所以需要收集當Promise狀態改變時,對應需要執行哪些回調,需用數組進行收集;
    • 如果then是放到定時器中調用的,那么改then的回調是不會被調用的,因為在前面我們是通過將回調添加到微任務中執行的,而定時器是宏任務,會在微任務執行完成后執行,所以定時器中then的回調就沒有被調用;
    • 當then是放到定時器中執行的,那么執行的時候,微任務已經執行完成了,Promise狀態肯定也確定下來了,那么只需要直接調用then中的回調即可;
class MyPromise {
  constructor(executor) {
    // 初始化Promise的狀態為pending
    this.promiseStatus = PENDING_STATUS
    // 初始化變量,用於保存resolve和reject傳入的參數值
    this.value = undefined
    this.reason = undefined
    // 初始化兩個數組,分別用於保存then中對應需要執行的回調
    this.onFulfilledFns = []
    this.onRejectedFns = []

    // 1.定義executor需要傳入的resolve函數
    const resolve = (value) => {
      // 只有當Promise的狀態為pending,才能將狀態改變fulfilled
      if (this.promiseStatus === PENDING_STATUS) {
        // 添加微任務
        queueMicrotask(() => {
          // 如果Promise狀態不為pending,后面的代碼就不再執行了
          if (this.promiseStatus !== PENDING_STATUS) return
          this.promiseStatus = FULFILLED_STATUS
          this.value = value
          // 狀態變成fulfilled就去遍歷調用onFulfilled
          this.onFulfilledFns.forEach(fn => {
            fn(this.value)
          })
        })
      }
    }

    // 2.定義executor需要傳入的reject函數
    const reject = (reason) => {
      // 只有當Promise的狀態為pending,才能將狀態改變為rejected
      if (this.promiseStatus === PENDING_STATUS) {
        // 添加微任務
        queueMicrotask(() => {
          // 如果Promise狀態不為pending,后面的代碼就不再執行了
          if (this.promiseStatus !== PENDING_STATUS) return
          this.promiseStatus = REJECTED_STATUS
          this.reason = reason
          // 狀態變成rejected就去遍歷調用onRejected
          this.onRejectedFns.forEach(fn => {
            fn(this.reason)
          })
        })
      }
    }

    // 3.將定義的兩個函數傳入executor並調用
    executor(resolve, reject)
  }

  then(onFulfilled, onRejected) {
    // 1.如果在調用then時,Promise的狀態已經確定了,就直接執行回調
    if (this.promiseStatus === FULFILLED_STATUS && onFulfilled) {
      onFulfilled(this.value)
    }
    if (this.promiseStatus === REJECTED_STATUS && onRejected) {
      onRejected(this.reason)
    }

    // 2.如果調用then時,Promise的狀態還沒確定下來,就放入微任務中執行
    if (this.promiseStatus === PENDING_STATUS) {
      // 將then中成功的回調和失敗的回調分別存入數組中
      this.onFulfilledFns.push(onFulfilled)
      this.onRejectedFns.push(onRejected)
    }
  }
}

簡單測試一下:

const p = new MyPromise((resolve, reject) => {
  resolve('aaaa')
  reject('err message')
})

p.then(res => {
  console.log('res1:', res)
}, err => {
  console.log('err1:', err)
})

p.then(res => {
  console.log('res2:', res)
}, err => {
  console.log('err1:', err)
})

setTimeout(() => {
  p.then(res => {
    console.log('res3:', res)
  }, err => {
    console.log('err1:', err)
  })
})

(3)then優化二

  • 通過上一步優化,then方法還存在一個缺陷,就是不能進行鏈式調用,在前面講then方法時,then方法執行的返回值是一個promise對象,並且返回的promise狀態是由then方法中回調函數的返回值決定的,then中必定需要返回一個新的Promise
  • 上一個then中回調的返回值可以傳遞到下一個then中成功的回調中,也就是返回的promise執行了resolve方法,那么什么時候可以傳遞到下一個then中失敗的回調中呢?只需要上一個then中拋出異常即可,相當於返回的promise執行了reject方法;
  • 所以,在這里需要拿到then中回調函數返回的結果,並且需要通過try catch判斷是調用resolve還是reject;
  • 注意:如果是在executor中就拋出了異常,也需要通過try catch去執行executor;
class MyPromise {
  constructor(executor) {
    // 初始化Promise的狀態為pending
    this.promiseStatus = PENDING_STATUS
    // 初始化變量,用於保存resolve和reject傳入的參數值
    this.value = undefined
    this.reason = undefined
    // 初始化兩個數組,分別用於保存then中對應需要執行的回調
    this.onFulfilledFns = []
    this.onRejectedFns = []

    // 1.定義executor需要傳入的resolve函數
    const resolve = (value) => {
      // 只有當Promise的狀態為pending,才能將狀態改變fulfilled
      if (this.promiseStatus === PENDING_STATUS) {
        // 添加微任務
        queueMicrotask(() => {
          // 如果Promise狀態不為pending,后面的代碼就不再執行了
          if (this.promiseStatus !== PENDING_STATUS) return
          this.promiseStatus = FULFILLED_STATUS
          this.value = value
          // 狀態變成fulfilled就去遍歷調用onFulfilled
          this.onFulfilledFns.forEach(fn => {
            fn(this.value)
          })
        })
      }
    }

    // 2.定義executor需要傳入的reject函數
    const reject = (reason) => {
      // 只有當Promise的狀態為pending,才能將狀態改變為rejected
      if (this.promiseStatus === PENDING_STATUS) {
        // 添加微任務
        queueMicrotask(() => {
          // 如果Promise狀態不為pending,后面的代碼就不再執行了
          if (this.promiseStatus !== PENDING_STATUS) return
          this.promiseStatus = REJECTED_STATUS
          this.reason = reason
          // 狀態變成rejected就去遍歷調用onRejected
          this.onRejectedFns.forEach(fn => {
            fn(this.reason)
          })
        })
      }
    }

    // 3.將定義的兩個函數傳入executor並調用
    // 如果executor中就拋出了異常,那么直接執行reject即可
    try {
      executor(resolve, reject)
    } catch (err) {
      reject(err)
    }
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      // 1.如果在調用then時,Promise的狀態已經確定了,就直接執行回調
      if (this.promiseStatus === FULFILLED_STATUS && onFulfilled) {
        // 通過try catch捕獲異常,沒有捕獲到執行resolve,捕獲到執行reject
        try {
          const value = onFulfilled(this.value)
          resolve(value)
        } catch (err) {
          reject(err)
        }
      }
      if (this.promiseStatus === REJECTED_STATUS && onRejected) {
        try {
          const reason = onRejected(this.reason)
          resolve(reason)
        } catch(err) {
          reject(err)
        }
      }

      // 2.如果調用then時,Promise的狀態還沒確定下來,就放入微任務中執行
      if (this.promiseStatus === PENDING_STATUS) {
        // 將then中成功的回調和失敗的回調分別存入數組中
        // 將傳入的回調外包裹一層函數,目的是為了這里能夠拿到then中回調執行的結果
        this.onFulfilledFns.push(() => {
          try {
            const value = onFulfilled(this.value)
            resolve(value)
          } catch(err) {
            reject(err)
          }
        })
        this.onRejectedFns.push(() => {
          try {
            const reason = onRejected(this.reason)
            resolve(reason)
          } catch(err) {
            reject(err)
          }
        })
      }
    })
  }
}

簡單測試一下:

const p = new MyPromise((resolve, reject) => {
  resolve('aaaa')
})

p.then(res => {
  console.log('res1:', res)
  return 'bbbb'
}, err => {
  console.log('err1:', err)
}).then(res => {
  console.log('res2:', res)
  throw new Error('err message')
}, err => {
  console.log('err2:', err)
}).then(res => {
  console.log('res3:', res)
}, err => {
  console.log('err3:', err)
})

7.3.catch方法的實現

  • catch方法的功能類似於then方法中的失敗回調,所以,實現catch方法只需要調用then,給then傳入失敗的回調即可;
  • 只給then傳入一個回調,意味着根據上面的代碼,我們還需要對then的回調進行條件判斷,有值才添加到對應數組中;
  • 注意:在then后鏈式調用catch會有一個問題,調用catch方法的promise是then執行之后返回的新promise,而catch真正需要去調用的是當前then的失敗回調,而不是當前then執行后結果promise的失敗回調,所以,可以將當前then的失敗回調推到下一次的promise中,而拋出異常就可以實現(因為上一個then拋出異常,可以傳遞到下一個then的失敗回調中
// catch方法實現
catch(onRejected) {
  return this.then(undefined, onRejected)
}

then方法改進:

簡單測試一下:

const p = new MyPromise((resolve, reject) => {
  reject('err message')
})

p.then(res => {
  console.log(res)
}).catch(err => {
  console.log(err) // err message
})

7.4.finally方法的實現

  • finally方法不管是Promise狀態變成fulfilled還是rejected都會被執行;
  • 這里可以巧妙的借助then方法,不管then是執行成功的回調還是失敗的回調,都去執行finally中的回調即可;
  • 注意:如果在finally之前使用了catch,因為catch的實現也是去調用then,並且給then的成功回調傳遞的是undefined,那么執行到catch可能出現斷層的現象,導致不會執行到finally,也可以通過在then中添加判斷解決;
finally(onFinally) {
  this.then(() => {
    onFinally()
  }, () => {
    onFinally()
  })
  // 也可直接簡寫成:
  // this.then(onFinally, onFinally)
}

then方法改進:

簡單測試一下:

const p = new MyPromise((resolve, reject) => {
  resolve('aaaa')
})

p.then(res => {
  console.log('res:', res) // res: aaaa
}).catch(err => {
  console.log('err:', err)
}).finally(() => {
  console.log('我是一定會執行的!') // 我是一定會執行的!
})

7.5.resolve和reject方法的實現

  • resolve和reject類方法的實現就是去調用Promise中executor中的resolve和reject;
  • 注意:類方法需要加上static關鍵字;
static resolve(value) {
  return new MyPromise((resolve, reject) => resolve(value))
}

static reject(reasion) {
  return new MyPromise((resolve, reject) => reject(reasion))
}

簡單測試一下:

MyPromise.resolve('aaaa').then(res => {
  console.log(res) // aaaa
})
MyPromise.reject('bbbb').then(res => {
  console.log(res)
}, err => {
  console.log(err) // bbbb
})

7.6.all方法的實現

  • all方法可接收一個promise數組,當所有promise狀態都變為fulfilled,就返回所有promise成功的回調值(一個數組),當其中有一個promise狀態變為了rejected,就返回該promise的狀態;
  • all實現的關鍵:當所有promise狀態變為fulfilled就去調用resolve,當有一個promise狀態變為rejected就去調用reject
static all(promises) {
  return new MyPromise((resolve, reject) => {
    // 用於存放所有成功的返回值
    const results = []
    promises.forEach(promise => {
      promise.then(res => {
        results.push(res)

        // 當成功返回值的長度與傳入promises的長度相等,就調用resolve
        if (results.length === promises.length) {
          resolve(results)
        }
      }, err => {
        // 一旦有一個promise變成了rejected狀態,就調用reject
        reject(err)
      })
    })
  })
}

簡單測試一下:

const p1 = new MyPromise(resolve => {
  setTimeout(() => {
    resolve('aaaa')
  }, 1000)
})
const p2 = new MyPromise(resolve => {
  setTimeout(() => {
    resolve('bbbb')
  }, 2000)
})
const p3 = new MyPromise(resolve => {
  setTimeout(() => {
    resolve('cccc')
  }, 3000)
})

MyPromise.all([p1, p2, p3]).then(res => {
  console.log(res) // [ 'aaaa', 'bbbb', 'cccc' ]
}).catch(err => {
  console.log(err)
})

7.7.allSettled方法的實現

  • allSettled方法會返回所有promise的結果數組,數組中包含每一個promise的狀態和值;
  • 不管promise的狀態為什么,最終都會調用resolve;
static allSettled(promises) {
  return new MyPromise((resolve, reject) => {
    // 用於存放所有promise的狀態和返回值
    const results = []
    promises.forEach(promise => {
      promise.then(res => {
        results.push({ status: FULFILLED_STATUS, value: res })
        // 當長度相等,調用resolve
        if (results.length === promises.length) {
          resolve(results)
        }
      }, err => {
        results.push({ status: REJECTED_STATUS, value: err })
        // 當長度相等,調用resolve
        if (results.length === promises.length) {
          resolve(results)
        }
      })
    })
  })
}

簡單測試一下:

const p1 = new MyPromise(resolve => {
  setTimeout(() => {
    resolve('aaaa')
  }, 1000)
})
const p2 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    reject('err message')
  }, 2000)
})
const p3 = new MyPromise(resolve => {
  setTimeout(() => {
    resolve('bbbb')
  }, 3000)
})

MyPromise.allSettled([p1, p2, p3]).then(res => {
  console.log(res)
}).catch(err => {
  console.log(err)
})

7.8.race方法的實現

  • race方法是獲取最先改變狀態的Promise,並以該Promise的狀態作為自己的狀態;
static race(promises) {
  return new MyPromise((resolve, reject) => {
    promises.forEach(promise => {
      // 得到狀態最先改變的promise,調用對應的resolve和reject
      promise.then(res => {
        resolve(resolve)
      }, err => {
        reject(err)
      })
    })
  })
}

7.9.any方法的實現

  • any方法會等到有一個Promise的狀態變成fulfilled,最終就是fulfilled狀態;
  • 如果傳入的所有Promise都為rejected狀態,會返回一個AggregateError,並且可以在AggregateError中的errors屬性中獲取所有錯誤信息;
static any(promises) {
  return new MyPromise((resolve, reject) => {
    // 用於記錄狀態為rejected的值
    const reasons = []
    promises.forEach(promise => {
      promise.then(res => {
        // 當有一個promise變成fulfilled狀態就調用resolve
        resolve(res)
      }, err => {
        reasons.push(err)
        // 當所有promise都是rejected就調用reject,並且傳入AggregateError
        if (reasons.length === promises.length) {
          reject(new AggregateError(reasons))
        }
      })
    })
  })
}

簡單測試一下:

const p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    reject('err message1')
  }, 1000)
})
const p2 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    reject('err message2')
  }, 2000)
})
const p3 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    reject('err message3')
  }, 3000)
})

MyPromise.any([p1, p2, p3]).then(res => {
  console.log(res)
}).catch(err => {
  console.log('err:', err)
  console.log(err.errors)
})

7.10.Promise手寫完整版整理

上面已經對Promise的各個功能進行了實現,下面就來整理一下最終的完整版,可以將一些重復的邏輯抽取出去,比如try catch。

// 定義Promise的三種狀態常量
const PENDING_STATUS = 'pending'
const FULFILLED_STATUS = 'fulfilled'
const REJECTED_STATUS = 'rejected'
// try catch邏輯抽取
function tryCatchFn(execFn, value, resolve, reject) {
  try {
    const result = execFn(value)
    resolve(result)
  } catch (err) {
    reject(err)
  }
}
class MyPromise {
  constructor(executor) {
    // 初始化Promise的狀態為pending
    this.promiseStatus = PENDING_STATUS
    // 初始化變量,用於保存resolve和reject傳入的參數值
    this.value = undefined
    this.reason = undefined
    // 初始化兩個數組,分別用於保存then中對應需要執行的回調
    this.onFulfilledFns = []
    this.onRejectedFns = []

    // 1.定義executor需要傳入的resolve函數
    const resolve = (value) => {
      // 只有當Promise的狀態為pending,才能將狀態改變fulfilled
      if (this.promiseStatus === PENDING_STATUS) {
        // 添加微任務
        queueMicrotask(() => {
          // 如果Promise狀態不為pending,后面的代碼就不再執行了
          if (this.promiseStatus !== PENDING_STATUS) return
          this.promiseStatus = FULFILLED_STATUS
          this.value = value
          // 狀態變成fulfilled就去遍歷調用onFulfilled
          this.onFulfilledFns.forEach(fn => {
            fn(this.value)
          })
        })
      }
    }

    // 2.定義executor需要傳入的reject函數
    const reject = (reason) => {
      // 只有當Promise的狀態為pending,才能將狀態改變為rejected
      if (this.promiseStatus === PENDING_STATUS) {
        // 添加微任務
        queueMicrotask(() => {
          // 如果Promise狀態不為pending,后面的代碼就不再執行了
          if (this.promiseStatus !== PENDING_STATUS) return
          this.promiseStatus = REJECTED_STATUS
          this.reason = reason
          // 狀態變成rejected就去遍歷調用onRejected
          this.onRejectedFns.forEach(fn => {
            fn(this.reason)
          })
        })
      }
    }

    // 3.將定義的兩個函數傳入executor並調用
    // 如果executor中就拋出了異常,那么直接執行reject即可
    try {
      executor(resolve, reject)
    } catch (err) {
      reject(err)
    }
  }

  then(onFulfilled, onRejected) {
    // 判斷onRejected是否有值,沒有值的話直接賦值一個拋出異常的方法,用於傳遞到下一次then中的失敗回調,供catch調用
    onRejected = onRejected || (err => {throw err})

    // 判斷onFulfilled是否有值,避免在使用catch時傳入的undefined不會執行,出現斷層現象
    onFulfilled = onFulfilled || (value => value)

    return new MyPromise((resolve, reject) => {
      // 1.如果在調用then時,Promise的狀態已經確定了,就直接執行回調
      if (this.promiseStatus === FULFILLED_STATUS) {
        // 通過try catch捕獲異常,沒有捕獲到執行resolve,捕獲到執行reject
        tryCatchFn(onFulfilled, this.value, resolve, reject)
      }
      if (this.promiseStatus === REJECTED_STATUS) {
        tryCatchFn(onRejected, this.reason, resolve, reject)
      }

      // 2.如果調用then時,Promise的狀態還沒確定下來,就放入微任務中執行
      if (this.promiseStatus === PENDING_STATUS) {
        // 將then中成功的回調和失敗的回調分別存入數組中
        // 將傳入的回調外包裹一層函數,目的是為了這里能夠拿到then中回調執行的結果
        this.onFulfilledFns.push(() => {
          tryCatchFn(onFulfilled, this.value, resolve, reject)
        })
        this.onRejectedFns.push(() => {
          tryCatchFn(onRejected, this.reason, resolve, reject)
        })
      }
    })
  }

  catch(onRejected) {
    return this.then(undefined, onRejected)
  }

  finally(onFinally) {
    this.then(onFinally, onFinally)
  }

  static resolve(value) {
    return new MyPromise((resolve, reject) => resolve(value))
  }

  static reject(reasion) {
    return new MyPromise((resolve, reject) => reject(reasion))
  }

  static all(promises) {
    return new MyPromise((resolve, reject) => {
      // 用於存放所有成功的返回值
      const results = []
      promises.forEach(promise => {
        promise.then(res => {
          results.push(res)

          // 當成功返回值的長度與傳入promises的長度相等,就調用resolve
          if (results.length === promises.length) {
            resolve(results)
          }
        }, err => {
          // 一旦有一個promise變成了rejected狀態,就調用reject
          reject(err)
        })
      })
    })
  }

  static allSettled(promises) {
    return new MyPromise((resolve, reject) => {
      // 用於存放所有promise的狀態和返回值
      const results = []
      promises.forEach(promise => {
        promise.then(res => {
          results.push({ status: FULFILLED_STATUS, value: res })
          // 當長度相等,調用resolve
          if (results.length === promises.length) {
            resolve(results)
          }
        }, err => {
          results.push({ status: REJECTED_STATUS, value: err })
          // 當長度相等,調用resolve
          if (results.length === promises.length) {
            resolve(results)
          }
        })
      })
    })
  }

  static race(promises) {
    return new MyPromise((resolve, reject) => {
      promises.forEach(promise => {
        // 得到狀態最先改變的promise,調用對應的resolve和reject
        promise.then(resolve, reject)
      })
    })
  }

  static any(promises) {
    return new MyPromise((resolve, reject) => {
      // 用於記錄狀態為rejected的值
      const reasons = []
      promises.forEach(promise => {
        promise.then(res => {
          // 當有一個promise變成fulfilled狀態就調用resolve
          resolve(res)
        }, err => {
          reasons.push(err)
          // 當所有promise都是rejected就調用reject,並且傳入AggregateError
          if (reasons.length === promises.length) {
            reject(new AggregateError(reasons))
          }
        })
      })
    })
  }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM