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

小明同學喲發表於2020-01-19

前言

小夥伴們大家好,這裡我將帶大家手寫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

相關文章