一步步教你實現Promise/A+ 規範 完整版

前言

小夥伴們大家好,這裏我將帶大家手寫Promise,作爲前端的開發者,在日常的工作中,肯定避免不了一個問題,那就是異步編程

那麼什麼是異步編程?

cmd-markdown-logo

  • 從服務器獲取數據,這個過程就叫做異步編程

  • 在node.js中去讀取文件,這個過程也是異步的

關於異步的解決方案目前有四種:

  • callback(回調函數)
  • generato + co庫
  • promise
  • async+await

今天就來爲大家重要講解一下promise,徹底的弄懂它
cmd-markdown-logo

學習之前

在學習之前我們需要弄懂以下的:

  • 瞭解Promise
  • es6 (主要是es6的箭頭函數和es6的類)
  • this的指向問題
  • 關於promise/A+規範
參考文檔:https://promisesaplus.com/

術語

解決(fulfill)

指一個 promise 成功時進行的一系列操作,如狀態的改變、回調的執行。雖然規範中用 fulfill 來表示解決,但在後世的 promise 實現多以 resolve 來指代之

拒絕(reject)

指一個 promise失敗時進行的一系列操作

終值(eventual value)

所謂終值,指的是 promise 被解決時傳遞給解決回調的值,由於 promise 有一次性的特徵,因此當這個值被傳遞時,標誌着 promise 等待態的結束,故稱之終值,有時也直接簡稱爲值(value)

據因(reason)

也就是拒絕原因,指在 promise 被拒絕時傳遞給拒絕回調的值

Promise

promise是一個擁有then方法的對象或函數,其行爲符合本規範

thenable

是一個定義了then方法的對象或函數

值(value)

指任何JavaScript的合法值(包括undefined、thenable和promise)

異常(exception)

是適用throw語句拋出的一個值

異步回調

回調地域

在需要多個操作的時候,會導致多個回調函數嵌套,導致代碼不夠直觀,就是常說的回調地獄

並行結果

如果幾個異步操作之間並沒有前後順序之分,但需要等多個異步操作都完成後才能執行後續的任務,無法實現並行節約時間

promise理解

Promise本意是承諾,在程序中的意思就是承諾我過一段時間後會給你一個結果:

什麼時候會用到過一段時間?

是異步操作

異步是指可能比較長時間纔有結果的才做,例如網絡請求、讀取本地文件等

Promise的狀態

一個Promise的當前狀態必須爲以下三種狀態中的一種

等待態(Pending)

處於等待態時,promise需滿足以下條件:

可以遷移至執行態或拒絕態

執行態(Fulfilled)

處於執行態時,promise 需滿足以下條件:

不能遷移至其他任何狀態

必須擁有一個不可變的終值

拒絕態(Rejected)

處於拒絕態時,promise需要滿足以下條件

不能遷移至其他任何狀態

必須擁有一個不可變的據因

Then方法

一個promise必須提供一個then方法以訪問其當前值、終值和據因

promise的then方法接受兩個參數:

promise.then(onFulfilled, onRejected)

onFulfilled 和 onRejected 都是可選參數

  • 如果onFullfilled不是函數,其必須被忽略
  • 如果onRejected不是函數,其必須被忽略

onFulfilled特性

如果onFulfilled是函數:

  • 當promise執行結束後其必須被調用,其第一個參數爲promise的終值
  • 在promise執行結束前其不可被調用
  • 其調用次數不可超過一次

onRejected特性

如果onRejected是函數:

  • 當 promise 被拒絕執行後其必須被調用,其第一個參數爲 promise 的據因
  • 在 promise 被拒絕執行前其不可被調用
  • 其調用次數不可超過一次

調用時機

onFulfilled 和 onRejected 只有在執行環境堆棧僅包含平臺代碼時纔可被調用

調用要求

onFulfilled 和 onRejected 必須被作爲函數調用(即沒有 this 值)

多次調用

then 方法可以被同一個promise調用多次

  • 當 promise 成功執行時,所有 onFulfilled 需按照其註冊順序依次回調

  • 當 promise 被拒絕執行時,所有的 onRejected 需按照其註冊順序依次回調

返回

then 方法必須返回一個 promise 對象

準備

在開始之前我們需要創建三個文件

  • index.js進行原生的Promise演示
  • promise.js進行自定義的Promise演示
  • test.js是對promise.js進行測試

promise初體驗

一個最基本的Promise長什麼樣?

代碼如下:

index.js

new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1)
    })
}).then(value => {
    console.log('value',value)
},reason => {
    console.log('reason',reason)
})

運行結果:

value 1

原生的promise它的參數不是函數的時,會發生什麼?

代碼如下:

index.js

new Promise(1)

promise.js

class Promise {
    constructor(executor){
        //參數效驗
        if(typeof executor !== 'function'){
            throw new TypeError('Promise resolver ${executor} is not a function')
        }
    }
}

運行結果:

Promise resolver 1 is not a function

這是一個最基本的promise

代碼如下

index.js

new Promise((resolve, reject) => {
    console.log('早上好!')
        resolve(1)
})

promise.js

class Promise {
    constructor(executor){
        //參數效驗
        if(typeof executor !== 'function'){
            throw new TypeError('Promise resolver ${executor} is not a function')
        }
        const resolve = function (){

        }
        const reject = function (){

        }
        executor(resolve,reject)
    }
}

運行結果:

早上好!

再把上邊代碼測試一下

代碼如下:

promise.js

class Promise {
  constructor(executor){
      //不能相信用戶的輸入,所以這裏要做參數效驗
      if(typeof executor !== 'function'){
          throw new TypeError('Promise resolver ${executor} is not a function')
      }
      //記錄狀態和值的改變
      //初始化值
      this.value = null //終值
      this.reason = null //拒因
      this.state = 'pending' //狀態

      const resolve = value => {
          //成功後的一系列操作(狀態的改變,成功回調的執行)
          if(this.state === 'pending'){
              //狀態進行改變
              this.state = 'fulfilled'
              //執行成功的回調,把終值進行賦值
              this.value = value
          }
      }
      const reject = reason =>{
          //失敗後的一系列操作(狀態的改變,失敗回調的執行)
          if(this.state === 'pending'){
              //狀態進行改變
              this.state = 'rejected'
              //執行成功的回調,把據因進行賦值
              this.reason = reason
          }
      }
      executor(resolve,reject)
  }
}
module.exports = Promise

test.js

const Promise = require('./promise.js')

new Promise((resolve, reject) => {
    console.log('早上好!')
        resolve(1)
})

運行結果:

早上好!

promise初步實現

把上邊的代碼進行優化

優化後的代碼如下

promise.js

class Promise {
    constructor(executor){
        //不能相信用戶的輸入,所以這裏要做參數效驗
        if(typeof executor !== 'function'){
            throw new TypeError('Promise resolver ${executor} is not a function')
        }

        this.initValue()
        this.initBind()

        executor(this.resolve,this.reject)
    }
    //綁定 this
    initBind(){
        this.resolve = this.resolve.bind(this)
        this.reject = this.reject.bind(this)
    }
    //進行代碼的優化
    initValue(){
        //記錄狀態和值的改變
        //初始化值
        this.value = null //終值
        this.reason = null //拒因
        this.state = 'pending' //狀態
    }
    resolve(value){
        //成功後的一系列操作(狀態的改變,成功回調的執行)
        if(this.state === 'pending'){
            //狀態進行改變
            this.state = 'fulfilled'
            //執行成功的回調,把終值進行賦值
            this.value = value
        }
    }
    reject(reason){
        //失敗後的一系列操作(狀態的改變,失敗回調的執行)
        if(this.state === 'pending'){
            //狀態進行改變
            this.state = 'rejected'
            //執行成功的回調,把據因進行賦值
            this.reason = reason
        }
    }
    then() {}
}
module.exports = Promise

test.js

const Promise = require('./promise.js')

new Promise((resolve, reject) => {
    console.log('早上好!')
        resolve(1)
})

運行結果:

早上好!

測試通過後,下一步then方法

代碼如下:

promise.js

class Promise {
    constructor(executor){
        //不能相信用戶的輸入,所以這裏要做參數效驗
        if(typeof executor !== 'function'){
            throw new TypeError('Promise resolver ${executor} is not a function')
        }

        this.initValue()
        this.initBind()

        executor(this.resolve,this.reject)
    }
    //綁定 this
    initBind(){
        this.resolve = this.resolve.bind(this)
        this.reject = this.reject.bind(this)
    }
    //進行代碼的優化
    initValue(){
        //記錄狀態和值的改變
        //初始化值
        this.value = null //終值
        this.reason = null //拒因
        this.state = 'pending' //狀態
    }
    resolve(value){
        //成功後的一系列操作(狀態的改變,成功回調的執行)
        if(this.state === 'pending'){
            //狀態進行改變
            this.state = 'fulfilled'
            //執行成功的回調,把終值進行賦值
            this.value = value
        }
    }
    reject(reason){
        //失敗後的一系列操作(狀態的改變,失敗回調的執行)
        if(this.state === 'pending'){
            //狀態進行改變
            this.state = 'rejected'
            //執行成功的回調,把據因進行賦值
            this.reason = reason
        }
    }
    then(onFulfilled, onRejected) {
        //  參數效驗
        if (typeof onFulfilled !== 'function'){
            onFulfilled = function(value) {
                return value
            }
        }
        if (typeof onRejected !== 'function'){
            onRejected = function(reason){
                throw reason
            }
        }
        if(this.state === 'fulfilled'){
            onFulfilled(this.value)
        }
        if(this.state === 'rejected'){
            onRejected(this.reason)
        }
    }
}
module.exports = Promise

test.js

const Promise = require('./promise.js')

new Promise((resolve, reject) => {
    console.log('早上好!')
        resolve(1)
}).then(value=> {
    console.log('value',value)
},reason => {   
    console.log('reason',value)
})

運行結果:

早上好!
value 1

將上邊的代碼再次進行優化

代碼如下:

promise.js

class Promise {
    constructor(executor){
        //不能相信用戶的輸入,所以這裏要做參數效驗
        if(typeof executor !== 'function'){
            throw new TypeError('Promise resolver ${executor} is not a function')
        }

        this.initValue()
        this.initBind()

        executor(this.resolve,this.reject)
    }
    //綁定 this
    initBind(){
        this.resolve = this.resolve.bind(this)
        this.reject = this.reject.bind(this)
    }
    //進行代碼的優化
    initValue(){
        //記錄狀態和值的改變
        //初始化值
        this.value = null //終值
        this.reason = null //拒因
        this.state = Promise.PENDING //狀態
    }
    resolve(value){
        //成功後的一系列操作(狀態的改變,成功回調的執行)
        if(this.state === Promise.PENDING){
            //狀態進行改變
            this.state = Promise.FULFILLED
            //執行成功的回調,把終值進行賦值
            this.value = value
        }
    }
    reject(reason){
        //失敗後的一系列操作(狀態的改變,失敗回調的執行)
        if(this.state === 'pending'){
            //狀態進行改變
            this.state = Promise.REJECTED
            //執行成功的回調,把據因進行賦值
            this.reason = reason
        }
    }
    then(onFulfilled, onRejected) {
        //  參數效驗
        if (typeof onFulfilled !== 'function'){
            onFulfilled = function(value) {
                return value
            }
        }
        if (typeof onRejected !== 'function'){
            onRejected = function(reason){
                throw reason
            }
        }
        if(this.state === Promise.FULFILLED){
            onFulfilled(this.value)
        }
        if(this.state === Promise.REJECTED){
            onRejected(this.reason)
        }
    }
}
Promise.PENDING = 'pending'
Promise.FULFILLED = 'fulfilled'
Promise.REJECTED = 'reject'

module.exports = Promise

test.js

const Promise = require('./promise.js')

new Promise((resolve, reject) => {
    console.log('早上好!')
        resolve(1)
}).then(value=> {
    console.log('value',value)
},reason => {   
    console.log('reason',value)
})

運行結果:

早上好!
value 1

異步解決實現

先看一下這段代碼的執行順序是什麼樣的?

代碼如下

index.js

console.log('1')
new Promise((resolve, reject) => {
    console.log('2')
        resolve(1)
    }).then(value => {
    console.log('4')
    console.log('value',value)
},reason => {
    console.log('reason',reason)
})
console.log('3')

運行結果:

1
2
3
4
value 1

在測試文件運行這段代碼執行順序會是什麼樣?

代碼如下

test.js

const Promise = require('./promise.js')

console.log('1')
new Promise((resolve, reject) => {
    console.log('2')
        resolve(1)
    }).then(value => {
    console.log('4')//立即執行了
    console.log('value',value)//立即執行了
},reason => {
    console.log('reason',reason)
})
console.log('3')

運行結果:

1
2
4
value 1
3

如何來模擬異步呢?

在promise.js裏面添加代碼,利用setTimeout,再運行test.js

代碼如下:

promise.js

class Promise {
    constructor(executor){
        //不能相信用戶的輸入,所以這裏要做參數效驗
        if(typeof executor !== 'function'){
            throw new TypeError('Promise resolver ${executor} is not a function')
        }

        this.initValue()
        this.initBind()

        try{
            executor(this.resolve, this.reject)
        }catch(e){
            this.reject(e)
        }
    }
    //綁定 this
    initBind(){
        this.resolve = this.resolve.bind(this)
        this.reject = this.reject.bind(this)
    }
    //進行代碼的優化
    initValue(){
        //記錄狀態和值的改變
        //初始化值
        this.value = null //終值
        this.reason = null //拒因
        this.state = Promise.PENDING //狀態
    }
    resolve(value){
        //成功後的一系列操作(狀態的改變,成功回調的執行)
        if(this.state === Promise.PENDING){
            //狀態進行改變
            this.state = Promise.FULFILLED
            //執行成功的回調,把終值進行賦值
            this.value = value
        }
    }
    reject(reason){
        //失敗後的一系列操作(狀態的改變,失敗回調的執行)
        if(this.state === 'pending'){
            //狀態進行改變
            this.state = Promise.REJECTED
            //執行成功的回調,把據因進行賦值
            this.reason = reason
        }
    }
    then(onFulfilled, onRejected) {
        //  參數效驗
        if (typeof onFulfilled !== 'function'){
            onFulfilled = function(value) {
                return value
            }
        }
        if (typeof onRejected !== 'function'){
            onRejected = function(reason){
                throw reason
            }
        }
        if(this.state === Promise.FULFILLED){
            setTimeout(() => {
                onFulfilled(this.value)
            })
        }
        if(this.state === Promise.REJECTED){
            setTimeout(() => {
                onRejected(this.reason)
            })
        }
    }
}
Promise.PENDING = 'pending'
Promise.FULFILLED = 'fulfilled'
Promise.REJECTED = 'reject'

module.exports = Promise

test.js

const Promise = require('./promise.js')

console.log('1')
new Promise((resolve, reject) => {
    console.log('2')
        resolve(1)
    }).then(value => {
    console.log('4')
    console.log('value',value)
},reason => {
    console.log('reason',reason)
})
console.log('3')

運行結果:

1
2
3
4
value 1

假如在test.js裏面拋出一個異常,會是怎麼樣?

代碼如下:

test.js

const Promise = require('./promise.js')

console.log('1')
new Promise((resolve, reject) => {
    throw new Error('You write wrong')
    // console.log('2')
        resolve(1)
    }).then(value => {
    console.log('4')
    console.log('value',value)
},reason => {
    console.log('reason',reason)
})
console.log('3')

運行結果:

1
3
reason Error: You write wrong

上邊是直接在最外層進行一個拋出

假設我們把代碼放原生的promise裏,會是怎樣?

代碼如下:

index.js

const Promise = require('./promise.js')

console.log('1')
new Promise((resolve, reject) => {
    throw new Error('You write wrong')
    // console.log('2')
        resolve(1)
    }).then(value => {
    console.log('4')
    console.log('value',value)
},reason => {
    console.log('reason',reason)
})
console.log('3')

運行結果:

1
3
reason Error: You write wrong

如果是test.js裏是異步的會是什麼樣?

代碼如下:

test.js

const Promise = require('./promise.js')

console.log('1')
new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('hello!')
            resolve(1)
        })
    }).then(value => {
    console.log('4')
    console.log('value',value)
},reason => {
    console.log('reason',reason)
})
console.log('3')

運行結果:

1
3
hello!

不知道大家有沒有發現 ‘4’ 沒有執行,這是什麼原因呢?

因爲此時並沒有直接進入setTimeout裏面,而是進行了.then操作
cmd-markdown-logo
我們在promise.js裏面看到,

此時.then操作的狀態等於’pending’,

它不等於’fulfilled’,也不等於’reject’,

所以它並沒有執行這兩個回調函數中的任意一個,所以.then方法並沒有執行
cmd-markdown-logo
怎麼解決這個問題呢?

首先在promise.js裏面肯定要追加一個狀態的判斷

在promise.js裏追加一個狀態的判斷

代碼如下:

promise.js

class Promise {
    constructor(executor){
        //不能相信用戶的輸入,所以這裏要做參數效驗
        if(typeof executor !== 'function'){
            throw new TypeError('Promise resolver ${executor} is not a function')
        }

        this.initValue()
        this.initBind()

        try{
            executor(this.resolve, this.reject)
        }catch(e){
            this.reject(e)
        }
    }
    //綁定 this
    initBind(){
        this.resolve = this.resolve.bind(this)
        this.reject = this.reject.bind(this)
    }
    //進行代碼的優化
    initValue(){
        //記錄狀態和值的改變
        //初始化值
        this.value = null //終值
        this.reason = null //拒因
        this.state = Promise.PENDING //狀態
        this.onFulfilledCallbacks = []//成功回調
        this.onRejectedCallbacks = [] //失敗回調
    }
    resolve(value){
        //成功後的一系列操作(狀態的改變,成功回調的執行)
        if(this.state === Promise.PENDING){
            //狀態進行改變
            this.state = Promise.FULFILLED
            //執行成功的回調,把終值進行賦值
            this.value = value
            //成功或者失敗以後進行這兩個數組的執行
            this.onFulfilledCallbacks.forEach((fn) => fn(this.value)
            )}
    }
    reject(reason){
        //失敗後的一系列操作(狀態的改變,失敗回調的執行)
        if(this.state === 'pending'){
            //狀態進行改變
            this.state = Promise.REJECTED
            //執行成功的回調,把據因進行賦值
            this.reason = reason
            this.onRejectedCallbacks.forEach(fn => fn(this.reason))
        }
    }
    then(onFulfilled, onRejected) {
        //  參數效驗
        if (typeof onFulfilled !== 'function'){
            onFulfilled = function(value) {
                return value
            }
        }
        if (typeof onRejected !== 'function'){
            onRejected = function(reason){
                throw reason
            }
        }
        if(this.state === Promise.FULFILLED){
            setTimeout(() => {
                onFulfilled(this.value)
            })
        }
        if(this.state === Promise.REJECTED){
            setTimeout(() => {
                onRejected(this.reason)
            })
        }
        //在promise.js裏面肯定要追加一個狀態的判斷
        if(this.state === Promise.PENDING){
            this.onFulfilledCallbacks.push((value) => {
                setTimeout(() => {
                    onFulfilled(value)
                })
            })
            this.onRejectedCallbacks.push((reason) => {
                setTimeout(() => {
                    onRejected(this.reason)
                })
            })
        }
    }
}
Promise.PENDING = 'pending'
Promise.FULFILLED = 'fulfilled'
Promise.REJECTED = 'reject'

module.exports = Promise

運行結果:

1
3
hello!
4
value 1

鏈式調用的簡單解決方案

如何實現鏈式調用

代碼如下:

index.js

new Promise((resolve, reject) => {
        resolve(1)
    })
    .then(
        value => {
         return 'good' + value
    },
    reason => {
        console.log('reason',reason)
    }
    )
    .then(
        value => {
        console.log('value',value)
    },
    reason => {
        console.log('reason',reason)
}
)

運行結果:

value good1

如何才能做到鏈式調用呢?

實現鏈式調用,且改變了後面的then的值,必須通過新的實例

代碼如下:

index.js

new Promise((resolve, reject) => {
    // throw new Error('You write wrong')
    // console.log('2')
        resolve(1)
    })
    .then(
        value => {
         throw new Error('use')
         return 'good' + value
    },
    reason => {
        console.log('reason',reason)
    }
    )
    .then(
        value => {
        console.log('value',value)
    },
    reason => {
        console.log('reason',reason)
}
)

promise.js

class Promise {
    constructor(executor){
        //不能相信用戶的輸入,所以這裏要做參數效驗
        if(typeof executor !== 'function'){
            throw new TypeError('Promise resolver ${executor} is not a function')
        }

        this.initValue()
        this.initBind()

        try{
            executor(this.resolve, this.reject)
        }catch(e){
            this.reject(e)
        }
    }
    //綁定 this
    initBind(){
        this.resolve = this.resolve.bind(this)
        this.reject = this.reject.bind(this)
    }
    //進行代碼的優化
    initValue(){
        //記錄狀態和值的改變
        //初始化值
        this.value = null //終值
        this.reason = null //拒因
        this.state = Promise.PENDING //狀態
        this.onFulfilledCallbacks = []//成功回調
        this.onRejectedCallbacks = [] //失敗回調
    }
    resolve(value){
        //成功後的一系列操作(狀態的改變,成功回調的執行)
        if(this.state === Promise.PENDING){
            //狀態進行改變
            this.state = Promise.FULFILLED
            //執行成功的回調,把終值進行賦值
            this.value = value
            //成功或者失敗以後進行這兩個數組的執行
            this.onFulfilledCallbacks.forEach((fn) => fn(this.value)
            )}
    }
    reject(reason){
        //失敗後的一系列操作(狀態的改變,失敗回調的執行)
        if(this.state === 'pending'){
            //狀態進行改變
            this.state = Promise.REJECTED
            //執行成功的回調,把據因進行賦值
            this.reason = reason
            this.onRejectedCallbacks.forEach(fn => fn(this.reason))
        }
    }
    then(onFulfilled, onRejected) {
        //  參數效驗
        if (typeof onFulfilled !== 'function'){
            onFulfilled = function(value) {
                return value
            }
        }
        if (typeof onRejected !== 'function'){
            onRejected = function(reason){
                throw reason
            }
        }
        // 實現鏈式調用,且改變了後面的then的值,必須通過新的實例
        let promise2 = new Promise((resolve, reject) => {
            if(this.state === Promise.FULFILLED){
                setTimeout(() => {
                    try{
                        const x = onFulfilled(this.value)
                        resolve(x)
                    }catch(e){
                        reject(e)
                    }
                })
            }
            if(this.state === Promise.REJECTED){
                setTimeout(() => {
                    try{
                        const x = onRejected(this.reason)
                        resolve(x)
                    }catch(e){
                        reject(e)
                    }
                })
            }
            //在promise.js裏面肯定要追加一個狀態的判斷
            if(this.state === Promise.PENDING){
                this.onFulfilledCallbacks.push((value) => {
                    setTimeout(() => {
                        try{
                            const x = onFulfilled(value)
                            resolve(x)
                        }catch(e){
                            reject(e)
                        }
                    })
                })
                this.onRejectedCallbacks.push((reason) => {
                    setTimeout(() => {
                        try{
                            const x = onRejected(this.reason)
                            resolve(x)
                        }catch(e){
                            reject(e)
                        }
                    })
                })
            }
        })
        return promise2
    }
}
Promise.PENDING = 'pending'
Promise.FULFILLED = 'fulfilled'
Promise.REJECTED = 'reject'
Promise.resolvePromise = function(promise2, x, resolve, reject){}

module.exports = Promise

運行結果:

reason Error: use

鏈式調用的終極解決方案和測試

當返回值不是一個普通數值或一個基本類型,會是什麼樣情況?

代碼如下:

test.js

const Promise = require('./promise.js')

new Promise((resolve, reject) => {
    // throw new Error('You write wrong')
    // console.log('2')
        resolve(1)
    })
    .then(
        value => {
         return new Promise((resolve) => {
             resolve(1)
         })
    },
    reason => {
        console.log('reason',reason)
    }
    )
    .then(
        value => {
        console.log('value',value)
    },
    reason => {
        console.log('reason',reason)
}
)

運行結果:

value Promise {
  value: 1,
  reason: null,
  state: 'fulfilled',
  onFulfilledCallbacks: [],
  onRejectedCallbacks: [],
  resolve: [Function: bound resolve],
  reject: [Function: bound reject] }

分析結果可知:

當x的值不是基本值的時候,而是promise實例得時候,必須等待這一個promise的時候結束,才能進行進一步執行
cmd-markdown-logo
所以規範提出了一個解決方案
針對resolvePromise的具體解決過程
cmd-markdown-logo
首先是判斷promise2和x的值是否是相等的

如果相等的話,就拋出一個TypeError,是爲了避免循環調用的問題

我們可以通過代碼看一下:

index.js

let p1 = new Promise((resolve) => {
    resolve(1)
})
let p2 = p1.then(() => {
    return p2
})

運行結果:

TypeError: Chaining cycle detected for promise

鏈式調用出現了問題

在promise.js裏面處理一下

promise.js

class Promise {
    constructor(executor) {
      // 參數校檢
      if (typeof executor !== 'function') {
        throw new TypeError(`Promise resolver ${executor} is not a function`)
      }
  
      this.initValue()
      this.initBind()
  
      try {
        executor(this.resolve, this.reject)
      } catch (e) {
        this.reject(e)
      }
    }
  
    // 綁定 this
    initBind() {
      this.resolve = this.resolve.bind(this)
      this.reject = this.reject.bind(this)
    }
  
    // 初始化值
    initValue() {
      this.value = null // 終值
      this.reason = null // 拒因
      this.state = Promise.PENDING // 狀態
      this.onFulfilledCallbacks = [] // 成功回調
      this.onRejectedCallbacks = [] // 失敗回調
    }
  
    resolve(value) {
      // 成功後的一系列操作(狀態的改變, 成功回調的執行)
      if (this.state === Promise.PENDING) {
        this.state = Promise.FULFILLED
        this.value = value
        this.onFulfilledCallbacks.forEach(fn => fn(this.value))
      }
    }
  
    reject(reason) {
      // 失敗後的一系列操作(狀態的改變, 失敗回調的執行)
      if (this.state === 'pending') {
        this.state = Promise.REJECTED
        this.reason = reason
        this.onRejectedCallbacks.forEach(fn => fn(this.reason))
      }
    }
  
    then(onFulfilled, onRejected) {
      // 參數校檢
      if (typeof onFulfilled !== 'function') {
        onFulfilled = function(value) {
          return value
        }
      }
  
      if (typeof onRejected !== 'function') {
        onRejected = function(reason) {
          throw reason
        }
      }
  
      // 實現鏈式調用, 且改變了後面then的值, 必須通過新的實例
      let promise2 = new Promise((resolve, reject) => {
        if (this.state === Promise.FULFILLED) {
          setTimeout(() => {
            try {
              const x = onFulfilled(this.value)
              Promise.resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        }
  
        if (this.state === Promise.REJECTED) {
          setTimeout(() => {
            try {
              const x = onRejected(this.reason)
              Promise.resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        }
  
        if (this.state === Promise.PENDING) {
          this.onFulfilledCallbacks.push(value => {
            setTimeout(() => {
              try {
                const x = onFulfilled(value)
                Promise.resolvePromise(promise2, x, resolve, reject)
              } catch (e) {
                reject(e)
              }
            })
          })
  
          this.onRejectedCallbacks.push(reason => {
            setTimeout(() => {
              try {
                const x = onRejected(this.reason)
                Promise.resolvePromise(promise2, x, resolve, reject)
              } catch (e) {
                reject(e)
              }
            })
          })
        }
      })
  
      return promise2
    }
  }
  
  Promise.PENDING = 'pending'
  Promise.FULFILLED = 'fulfilled'
  Promise.REJECTED = 'reject'
  Promise.resolvePromise = function(promise2, x, resolve, reject) {
    // x 與 promise 相等
    if (promise2 === x) {
      reject(new TypeError('Chaining cycle detected for promise'))
    }
  
    let called = false
    if (x instanceof Promise) {
      // 判斷 x 爲 Promise
      x.then(
        value => {
          Promise.resolvePromise(promise2, value, resolve, reject)
        },
        reason => {
          reject(reason)
        }
      )
    } else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
      // x 爲對象或函數
      try {
        const then = x.then
        if (typeof then === 'function') {
          then.call(
            x,
            value => {
              if (called) return
              called = true
              Promise.resolvePromise(promise2, value, resolve, reject)
            },
            reason => {
              if (called) return
              called = true
              reject(reason)
            }
          )
        } else {
          if (called) return
          called = true
          resolve(x)
        }
      } catch (e) {
        if (called) return
        called = true
        reject(e)
      }
    } else {
      resolve(x)
    }
  }
  
  module.exports = Promise

test.js

const Promise = require('./promise.js')

new Promise((resolve, reject) => {
    // throw new Error('You write wrong')
    // console.log('2')
        resolve(1)
    })
    .then(
        value => {
         return new Promise((resolve) => {
             resolve(new Promise((resolve,reject) => {
                 resolve('333')
             })
             )
         })
    },
    reason => {
        console.log('reason',reason)
    }
    )
    .then(
        value => {
        console.log('then 2 value:',value)
    },
    reason => {
        console.log('reason',reason)
}
)

運行結果:

then 2 value: 333

cmd-markdown-logo

如何驗證我們的promise是否正確

首先

我們需要安裝一個promises-aplus-tests

npm install promises-aplus-tests

用來測試自己的promise 符不符合promisesA+規範

然後

把下邊這段代碼copy到promise.js裏面

Promise.defer = Promise.deferred = function () {
  let dfd = {}
  dfd.promise = new Promise((resolve,reject)=>{
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
}
module.exports = Promise;

最後

執行輸入npx promises-aplus-tests 你要測試的文件(promise.js)

npx promises-aplus-tests promise.js

cmd-markdown-logo
以上,我們就完成了一個基於Promise A+規範的Promise

最後擴展

源碼地址

https://github.com/shifengming/promise

動手寫一下

小夥伴們有興趣可以手動寫下面這些方法的實現

  • 手寫Promise.all()
  • 手寫Promise.race()
  • 手寫Promise.prototype.catch

最後

如果本文對你有幫助得話,給本文點個贊❤️❤️❤️

歡迎大家加入,一起學習前端,共同進步!
cmd-markdown-logo
cmd-markdown-logo

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