前端面試題之手寫promise


前端面試題之Promise問題

前言

在我們日常開發中會遇到很多異步的情況,比如涉及到 網絡請求(ajax,axios等)定時器這些,對於這些異步操作我們如果需要拿到他們操作后的結果,就需要使用到回調函數。拿請求來說,如果我們需要拿到請求回來的數據我們就需要利用回調函數(見代碼片段1),以下所有的請求都是使用jQuery的ajax模擬。

點擊查看代碼片段1
    // 代碼片段1
    $.ajax({
        url: 'url',
        type: 'post',
        data: {
            參數1: 值1,
            參數2: 值2
        },
        success: function(res){
            // success就是響應成功的回調函數,在此處可以獲取到響應返回的內容
            console.log(res)
        }
    })

在網絡請求中會遇到請求之前的依賴,一個請求會依賴於另一個請求的時候,就會需要出現回調嵌套的問題(代碼片段2)

點擊查看代碼片段2
    // 代碼片段2
    // 請求2的請求參數是請求1的響應結果
    // 請求3的請求參數是請求2的響應結果
    $.ajax({
        url: '請求1的地址',
        type: 'post',
        data: {
            參數1: 值1,
            參數2: 值2
        },
        success: function(res){
            // res是請求1的響應結果
            console.log(res)
            $.ajax({
                url: '請求2的地址',
                type: 'post',
                data: {
                    // 參數1是請求1的響應結果
                    參數1: res,
                    參數2: 值2
                },
                success: function(res2){
                    // res2是請求2的響應結果
                    $.ajax({
                        url: '請求3的地址',
                        type: 'post',
                        data: {
                            // 參數1是請求2的響應結果
                            參數1: res2,
                            參數2: 值2
                        },
                        success: function(res3){
                            // res3是最終請求的結果
                            console.log(res3)
                        }
                    })
                }
            })
        }
    })

這樣的代碼就出現了js編程里面的著名回調地獄問題,為了解決這個問題我們需要利用es2015的promise和es2016的await來解決(代碼片段3)

點擊查看代碼片段3
    // 代碼片段3
    // await不能寫在同步的代碼里面,會阻塞整個程序的進程,只能寫在異步的代碼里面
    // async可以修飾一個函數讓這個函數變成一個異步的函數
    async function getRes(){
        // $.ajax() 返回的是一個promise對象
        // await可以等待一個promise狀態結束,拿到響應的結果
        const res1 = await $.ajax({
            url: '請求1的地址',
            type: 'post',
            data: {
                參數1: 值1,
                參數2: 值2
            }
        })
        const res2 = await $.ajax({
            url: '請求2的地址',
            type: 'post',
            data: {
                參數1: res,
                參數2: 值2
            }
        })
        const res3 = await $.ajax({
            url: '請求3的地址',
            type: 'post',
            data: {
                參數1: res2,
                參數2: 值2
            }
        })
    }
    // 調用異步函數
    getRes()

利用promise和await就可以將之前的回調嵌套地獄改成同步的代碼方式,但是這里面大家要注意await的使用事項,await不允許出現在同步代碼塊里面,會阻塞同步代碼執行,必須寫在異步的代碼塊里面,加async讓getRes函數成為一個異步的函數,這樣getRes執行不會阻塞全局,getRes函數內部代碼就是一個同步的方式執行,就解決了回調嵌套的問題。本來故事到這里就該說goodBye了,但是現在很多面試,尤其是一些大廠的面試要求我們自己實現promise和await,這章節先給大家實現promise。

Promise的封裝

1. promise的介紹和使用

  • 狀態(state)

    • pending
      • promise的初始狀態,並且此狀態可以轉換成fulfilledrejected
    • fulfilled
      • promise的成功狀態,不可以轉換其他狀態,並且必須有一個不可改變的最終值value
    • rejected
      • promise的失敗狀態,不可以轉換其他狀態,並且必須有一個不可改變的原因reason
  • 術語

    • 解決(fulfill)

      當調用resolve方法時promise的狀態變成fulfill

    • 拒絕(reject)

      當調用reject方法時promise的狀態變成reject

    • 終值(value)

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

    • 拒因(reason)

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

  • 面向對象編程方式

    • Promise是面向對象編程方式,對應的構造函數是 Promise
    • 使用Promise的時候需要創建一個實例對象,使用實例對象的方法
    • Promise構造函數實例化的時候需要傳遞一個函數(handler),此函數里面可以接收兩個參數,一個函數叫做resolve,一個函數叫做reject
    • 調用resolve時候promise實例的狀態變為 fulfilled
    • 調用reject時候promise實例的狀態變為 rejected
    • 此處要注意一個promise實例只有一個狀態,也就是不能同時調用resolve和reject
點擊查看代碼
    // promise01就是實例化Promise構造函數得到的實例對象
    const promise01 = new Promise((resolve,reject)=>{
        // 此處調用resolve時promise狀態會變成fulfilled
        // 此處調用reject時promise狀態會變成rejected
    })
  • 實例對象的方法
    • then方法
      • 該方法有兩個參數,可選,分別對應onFulfilled,onRejected
      • 當狀態為fulfilled時,調用onFulfilled,得到一個終值 value
      • 當狀態為rejected時,調用onRejected,得到一個拒因 reason
    • catch方法
      • 該方法有一個參數,可選,對應onRejected
      • 當狀態為rejected時,調用onRejected,得到一個拒因
    • finally方法
      • 該方法有一個參數,可選
      • 該方法無論是promise狀態成功還是失敗都會調用
    • 注意:
      1. 實例方法可以實現鏈式調用,因為每一次方法調用完成之后都會返回一個promise實例
      2. then方法可以調用多次
點擊查看代碼
    const promise01 = new Promise((resolve,reject)=>{
    })
    // 使用實例對象方法 鏈式調用
    promise01.then(
        // onFulfilled
        function(value){
            // 當promise實例的狀態為fulfilled調用
        },
        // onRejected
        function(reason){
            // 當promise實例的狀態為rejected調用
        }  
    ).then(
        // then方法可以調用多次
        // 並且調用可以不傳遞參數
    ).catch(function(reason){
            // 當promise實例的狀態為rejected調用
    }).finally(function(){
        // 無論promise實例的狀態為fulfilled或者rejected都會調用
    })

2. 實現手寫Promise

  1. 實現Promise的構造函數

    • 構造函數需要參數,參數必須是一個函數類型 這里面我們叫做handler,這兒會對handler類型進行驗證,如果不是函數類型會拋出類型異常
    • handler函數需要兩個形式參數 對應的是類自身的兩個方法
      • resolve方法
        • 該方法只能在promise狀態為pending的時候調用,如果promise已經有了狀態,則不允許調用
        • 調用方法promise的狀態變為fulfilled
        • 調用該方法會得到一個終值value
        • 該方法可能異步調用
        • 該方法里面會用到實例的this,在調用的時候需要改變this的指向
      • reject方法
        • 該方法只能在promise狀態為pending的時候調用,如果promise已經有了狀態,則不允許調用
        • 調用方法promise的狀態變為rejected
        • 調用該方法會得到一個拒因reason
        • 該方法可能異步調用
        • 該方法里面會用到實例的this,在調用的時候需要改變this的指向
    • 實例里面屬性
      • value 終值
      • reason 拒因
      • state 狀態
        • 狀態可以作為靜態的屬性,也可以作為實例的屬性
        • 狀態會多次用於判斷,所以建議用常量保存
    • 實例里面的方法
      • then
      • catch
      • finally
    • 類自身的方法
      • resolve
      • reject
        // promise.js
        // 狀態會多次用於判斷,所以建議用常量保存
        const PENDING = 'pending'
        const FULFILLED = 'fulfilled'
        const REJECTED = 'rejected'
        class Promise{
            constructor(handler){
                // 構造函數需要參數,參數必須是一個函數類型 這里面我們叫做handler,這兒會對handler類型進行驗證,如果不是函數類型會拋出類型異常
                if(typeof handler !== 'function'){
                    throw new TypeError(`Promise構造函數的參數${handler}不是一個函數`)
                }
                this.state = PENDING // 用來存儲promise的狀態 初始化狀態為pending
                this.reason = undefined // 拒因
                this.value = undefined // 終值
                // resolve,reject方法里面會用到實例的this,在調用的時候需要改變this的指向
                handler(this.resolve.bind(this), this.reject.bind(this))
            }
            resolve(value){
                // 該方法只能在promise狀態為pending的時候調用,如果promise已經有了狀態,則不允許調用
                if(this.state !== PENDING) return
                // 調用方法promise的狀態變為fulfilled
                this.state = FULFILLED
                // 調用該方法會得到一個終值value
                this.value = value
            }
            reject(reason){
                // 該方法只能在promise狀態為pending的時候調用,如果promise已經有了狀態,則不允許調用
                if(this.state !== PENDING) return
                // 調用方法promise的狀態變為rejected 
                this.state = REJECTED
                // 調用該方法會得到一個拒因reason
                this.reason = reason
            }
        } 
    
        // 試錯
        const p = new Promise(1)
        console.log(p)
        // promise.js:9 Uncaught TypeError: Promise構造函數的參數1不是一個函數
        // 正確操作
        // 狀態成功
        const p = new Promise((resolve, reject) => {
            resolve()
        })
        console.log(p)
        // Promise {state: 'fulfilled', reason: undefined, value: undefined}
        // 狀態失敗
        const p = new Promise((resolve, reject) => {
            reject()
        })
        console.log(p)
        // Promise {state: 'rejected', reason: undefined, value: undefined}
    
  2. 實現實例then方法

  • then方法接受兩個參數,兩個參數類型是函數

    • onFulfilled
      • 需要對onFulfilled類型進行驗證,如果不是一個函數就設置為默認函數function(value){return value},這個函數是獲取終值
      • 該函數在promise狀態為fulfilled時候調用,傳入終值
    • onRejected
      • 需要對onRejected類型進行驗證,如果不是一個函數就設置為一個默認函數function(err){throw err},這個函數是獲取拒因
      • 該函數在promise狀態為rejected時候調用,傳入拒因
        // promise.js
            const PENDING = 'pending'
            const FULFILLED = 'fulfilled'
            const REJECTED = 'rejected'
            class Promise{
                constructor(handler){...}
                resolve(value){...}
                reject(reason){...}
                then(onFulfilled, onRejected){
                    // 參數可選要進行判斷 如果傳遞不是一個函數 默認一個函數
                    onFulfilled = typeof onFulfilled==='function' ? onFulfilled : value => value;
                    onRejected = typeof onRejected==='function' ? onRejected : err => {throw err};
                    if(this.state === FULFILLED){
                        // 當promise狀態為fulfilled時候調用onFulfilled
                        onFulfilled(this.value)
                    }
                    if(this.state === REJECTED){
                        // 當promise狀態為rejected時候調用onRejected
                        onRejected(this.reason)
                    }
                }
            }
    
        // test.js
            // 成功狀態
            const p = new Promise((resolve, reject) => {
                resolve('success')
            })
            p.then(
                res => {
                    console.log('調用了onFullfilled')
                    console.log(res)
                },
                err => {
                    console.log('調用了onRejected')
                    console.log(err)
                }
            )
            // 控制台輸出 調用了onFullfilled sucess
    
    
            // 失敗狀態
            const p = new Promise((resolve, reject) => {
                reject('error')
            })
            p.then(
                res => {
                    console.log(res)
                },
                err => {
                    console.log(err)
                }
            )
            // 控制台輸出 調用了onRejected error
    
    
            // handler函數異步調用resolve
            const p = new Promise((resolve, reject) => {
                setTimeout(()=>{
                    resolve('success')
                },3000)
            })
            p.then(
                res => {
                    console.log('調用了onFullfilled')
                    console.log(res)
                },
                err => {
                    console.log('調用了onRejected')
                    console.log(err)
                }
            )
            // 控制台沒有任何內容輸出 onFulfilled函數並沒有被調用
    
    • 此時經過測試,目前的promise可以正常的調用,此時針對於同步的調用沒有問題,但是在handler里面 resolvereject 大部分情況下都是異步調用的,比如說延遲3s調用,此時then里面的onFulfilledonRejected是無法執行的,因為then函數在執行的時候promise的狀態是pending,onFulfilledonRejected函數里面是做了判斷,沒有對pendng狀態進行處理,針對於pending狀態的需要處理
        // promise.js
        // ...
        class Promise {
            // 用來存儲狀態成功的回調函數
            // 用來存儲狀態失敗的回調函數
            successCallBack = null
            errorCallBack = null
            constructor(handler) {...}
            resolve(value) {
                // ...
                // 異步調用的時候執行存儲的onFulfilled函數,傳入終值
                this.successCallBack(this.value)
    
            }
            reject(reason) {
                // ...
                // 異步調用的時候執行存儲的onRejected函數,傳入拒因
                this.errorCallBack(this.reason)
            }
            then(onFulfilled, onRejected) {
                // ...
                if (this.state === PENDING) {
                    // 當resolve或者reject異步調用,then執行的時候promise狀態等待
                    // 將onFulfilled和onReject函數存儲起來
                    this.successCallBack = onFulfilled
                    this.errorCallBack = onRejected
                }
            }
        }
    
        // test.js
        // 異步調用resolve
        const p = new Promise((resolve, reject) => {
            setTimeout(()=>{
                resolve('success')
            },3000)
        })
        p.then(
            res => {
                console.log('調用了onFullfilled')
                console.log(res)
            },
            err => {
                console.log('調用了onRejected')
                console.log(err)
            }
        )
        // 3s后控制台輸出 調用了onFullfilled  success
    
    • 此時promise的handler就可以異步調用 resolvereject,then里面可以獲取到終值或據因
    • then函數支持多次調用,所以此時then的回調函數就不止一個,我們將之前的變量改成數組用來存儲then的回調函數
        // promise.js
        // ...
        class Promise {
            // 用數組的方式存儲失敗和成功的回調函數
            successCallBack = []
            errorCallBack = []
            constructor(handler) {...}
            resolve(value) {
                // ...
                // 遍歷數組將所有成功的函數執行
                this.successCallBack.forEach(fn=> fn())
            }
            reject(reason) {
                // ...
                // 遍歷數組將所有失敗的函數執行
                this.errorCallBack.forEach(fn=>fn())
            }
            then(onFulfilled, onRejected) {
                // ...
                if (this.state === PENDING) {
                    // 當resolve或者reject異步調用,then執行的時候promise狀態等待
                    // 將onFulfilled和onReject函數存儲起來
                    this.successCallBack.push(()=>{
                        onFulfilled(this.value)
                    }) 
                    this.errorCallBack.push(()=>{
                        onRejected(this.reason)
                    })
                }
            }
        }
    
    • 測試調用了兩次then,狀態成功之后兩次then的成功回調都會被執行
        // test.js
        // 異步調用resolve
        const p = new Promise((resolve, reject) => {
            setTimeout(()=>{
                resolve('success')
            },3000)
        })
        p.then(
            res => {
                console.log('調用了onFullfilled')
                console.log(res)
            },
            err => {
                console.log('調用了onRejected')
                console.log(err)
            }
        )
         p.then(
            res => {
                console.log('調用了onFullfilled01')
                console.log(res)
            },
            err => {
                console.log('調用了onRejected01')
                console.log(err)
            }
        )
        // 3s后控制台輸出 調用了onFullfilled  success  調用了onFullfilled01 success
    
    • 但是目前then不支持鏈式調用,需要進行鏈式調用的支持
    • 支持鏈式調用的條件
      1. 只有then函數調用返回一個promise對象,一個新的promise對象newPromise
      2. then函數有一個返回值 這個返回值result就是新promise的onFulfill或onRejected的值
    • 接下來我們需要判斷這個返回值result的類型
      1. 一個具體值
      2. 一個新的promise
    • 另外我們需要一個函數去處理這種鏈式調用的問題handlerChainPromise,這個函數接收參數如下
      1. result 老的promise的then返回值,作為新promise的onFulfill或onRejected
      2. resolve成功處理函數
      3. reject失敗處理函數
    class Promise {
        then(onFulfilled, onRejected) {
            // 每一次調用返回一個promise
            return new Promise((resolve,reject) => {
                // 參數可選要進行判斷 如果傳遞不是一個函數 默認一個函數
                onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
                onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
                if (this.state === PENDING) {
                    // 當resolve或者reject異步調用,then執行的時候promise狀態等待
                    // 將onFulfilled和onReject函數存儲起來
                    this.successCallBack.push(() => {
                        let result = onFulfilled(this.value)
                        this.handlerChainPromise(result, resolve, reject)
                    })
                    this.errorCallBack.push(() => {
                        let result = onRejected(this.reason)
                        this.handlerChainPromise(result, resolve, reject)
                    })
                }
                if (this.state === FULFILLED) {
                    // 當promise狀態為fulfilled時候調用onFulfilled
                    let result = onFulfilled(this.value)
                    this.handlerChainPromise(result, resolve, reject)
                }
                if (this.state === REJECTED) {
                    // 當promise狀態為rejected時候調用onRejected
                    let result = onRejected(this.reason)
                    this.handlerChainPromise(result, resolve, reject)
                }
            })
        }
        handlerChainPromise(result,resolve,reject){
            // 如果返回的是一個promise就調用它的then方法
            // 如果返回的是一個具體值就直接返回值
            if(result instanceof Promise){
                result.then(resolve, reject)
            }else{
                resolve(result)
            }
        }
    }
    
    • 測試代碼如下
        // 返回一個具體值(可以使任何類型)
        const p = new Promise((resolve, reject) => {
            setTimeout(()=>{
                resolve('success')
            },3000)
        })
        p.then(
            res => {
                return 11
            },
            err => {
                console.log(err)
            }
        ).then(
            res=>{
                console.log(res)
            }
        )
        // 返回一個promsie
        const p = new Promise((resolve, reject) => {
            setTimeout(()=>{
                resolve('success')
            },3000)
        })
        p.then(
            res => {
                return new Promise(resolve=>{
                    setTimeout(()=>{
                        resolve(22)
                    }, 1000)
                })
            },
            err => {
                console.log(err)
            }
        ).then(
            res=>{
                console.log(res)
            }
        )
    
    • 以上一個完整的promise就封裝成功了
    • 完整代碼如下
    // 狀態會多次用於判斷,所以建議用常量保存
    const PENDING = 'pending'
    const FULFILLED = 'fulfilled'
    const REJECTED = 'rejected'
    class Promise {
        // 用來存儲狀態成功的回調函數
        // 用來存儲狀態失敗的回調函數
        successCallBack = []
        errorCallBack = []
        constructor(handler) {
            // 構造函數需要參數,參數必須是一個函數類型 這里面我們叫做handler,這兒會對handler類型進行驗證,如果不是函數類型會拋出類型異常
            if (typeof handler !== 'function') {
                throw new TypeError(`Promise構造函數的參數${handler}不是一個函數`)
            }
            this.state = PENDING // 用來存儲promise的狀態 初始化狀態為pending
            this.reason = undefined // 拒因
            this.value = undefined // 終值
            // resolve,reject方法里面會用到實例的this,在調用的時候需要改變this的指向
            handler(this.resolve.bind(this), this.reject.bind(this))
        }
        resolve(value) {
            // 該方法只能在promise狀態為pending的時候調用,如果promise已經有了狀態,則不允許調用
            if (this.state !== PENDING) return
            // 調用方法promise的狀態變為fulfilled
            this.state = FULFILLED
            // 調用該方法會得到一個終值value
            this.value = value
            // 異步調用的時候執行存儲的onFulfilled函數,傳入終值
            this.successCallBack.forEach(fn => fn())
        }
        reject(reason) {
            // 該方法只能在promise狀態為pending的時候調用,如果promise已經有了狀態,則不允許調用
            if (this.state !== PENDING) return
            // 調用方法promise的狀態變為rejected 
            this.state = REJECTED
            // 調用該方法會得到一個拒因reason
            this.reason = reason
            // 異步調用的時候執行存儲的onRejected函數,傳入拒因
            this.errorCallBack.forEach(fn => fn())
        }
        then(onFulfilled, onRejected) {
            // 每一次調用返回一個promise
            return new Promise((resolve,reject) => {
                // 參數可選要進行判斷 如果傳遞不是一個函數 默認一個函數
                onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
                onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
                if (this.state === PENDING) {
                    // 當resolve或者reject異步調用,then執行的時候promise狀態等待
                    // 將onFulfilled和onReject函數存儲起來
                    this.successCallBack.push(() => {
                        let result = onFulfilled(this.value)
                        this.handlerChainPromise(result, resolve, reject)
                    })
                    this.errorCallBack.push(() => {
                        let result = onRejected(this.reason)
                        this.handlerChainPromise(result, resolve, reject)
                    })
                }
                if (this.state === FULFILLED) {
                    // 當promise狀態為fulfilled時候調用onFulfilled
                    let result = onFulfilled(this.value)
                    this.handlerChainPromise(result, resolve, reject)
                }
                if (this.state === REJECTED) {
                    // 當promise狀態為rejected時候調用onRejected
                    let result = onRejected(this.reason)
                    this.handlerChainPromise(result, resolve, reject)
                }
            })
        }
        handlerChainPromise(result,resolve,reject){
            // 如果返回的是一個promise就調用它的then方法
            // 如果返回的是一個具體值就直接返回值
            if(result instanceof Promise){
                result.then(resolve, reject)
            }else{
                resolve(result)
            }
        }
    }
    


免責聲明!

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



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