前端面試必備——異步(Promise)

1340.640.jpg

1.引言

經過手寫符合A+規範的promise,來深刻了解Promise,再結合相關面試題,爭取作到在面試的時候,若是問Promise,我們能全方位吊打面試官😁😁😁
下面的每個寫法都對應Promise的一些特性,不斷升級,瞭解原理後再作題就會發現很簡單了es6

2.極簡版promise

2.1 基礎特性

詳細介紹的話你們去看 阮一峯es6-promise,我這裏當你已經有必定的基礎了,而後咱們總結一下基本特性面試

new Promise((resolve,reject)=>{ //excutor
    setTiemout(()=>{
        resolve(1) //resolve中的值會傳遞到成功的回調函數參數中
    },1000)
}).then((val)=>{ //onFulfiled
    console.log(val)
},(e)=>{  //onRejected
    console.log(e)
})
  1. Promise對象初始狀態值爲pending
  2. 當即執行excutor,在excutor中能夠經過resolve,reject方法改變promise狀態,分別改成filfiled(成功)和rejected(失敗)
  3. 狀態一旦改變狀態就凝固了,沒法再變
  4. then方法中的回調函數會在狀態改變後執行,成功調成功回調,失敗調用失敗回調
  5. resolve中的值會傳遞到成功的回調函數參數中 (失敗相似)

2.2實現

思路:一、二、三、5都比較好實現,4的話採用發佈訂閱模式也能實現,發佈訂閱模式可看我這篇文章發佈訂閱模式npm

class Promise {
    constructor(executor) {
        this.status='pending' //三狀態
        this.value = undefined //參數
        this.reason = undefined
        this.onFulfilled = [] //發佈訂閱中儲存回調
        this.onRejected = []
        let resolve = (value)=>{
            if(this.status==='pending'){
                this.status = 'fulfilled'
                this.value = value
                this.onFulfilled.forEach(fn=>fn(this.value))  //發佈訂閱模式,異步一改變狀態則當即執行回調
            }

        }
        let reject = (reason)=>{
            if(this.status==='pending'){
                this.status = 'rejected'
                this.reason = reason
                this.onRejected.forEach(fn=>fn(this.reason))
            }
        }
        try{
            executor(resolve,reject)  //executor同步執行
        }catch (e) {
            reject(e)
        }

    }

    then(onFulfilled, onRejected) { // 若是then的時候 根據當前狀態執行成功或者失敗
        if(this.status==='fulfilled'){
            onFulfilled(this.value)
        }
        if(this.status==='rejected'){
            onRejected(this.reason)
        }
        if(this.status==='pending'){
            this.onFulfilled.push(onFulfilled) //發佈訂閱模式儲存異步回調
            this.onRejected.push(onRejected)
        }
    }

}

3.添加鏈式調用

3.1 鏈式特性

1.若是promise中的then方法,不管是成功仍是失敗,他的返回結果是一個普通的時候就會把這個結果傳遞給外層的then的下一個then的成功回調segmentfault

Promise.reject().then((val)=>{
    return 'ok'
},()=>{
    return 'err'
}).then((val)=>{
    console.log('ok' + val)
},(e)=>{
console.log('err' + e)
})
// okerr      第一個then失敗的回調返回的是普通值,仍是走第二個的then中成功回調

2.若是成功或者失敗的回調的返回值 返回是一個promise 那麼會讓這個promise執行 採用他的狀態數組

Promise.resolve().then(()=>{
    return new Promise((resolve)=>{
        setTimeout(()=>{
            resolve(1)
        },1000)
    })
}).then((val)=>{
    console.log(val)
})
//一秒後打印1

3.2實現

這一版主要是實現鏈式調用,稍微繞一點,可是理清楚了也不難
首先明確一下,then後面會返回一個新的Promise,因此才能執行鏈式調用
第一個比較繞的地方,怎麼讓第二個then裏面的回調執行?只要調用建立promise2時的resolve方法就好了
第二個標膠繞的地方就是參數是什麼?咱們看特性3.1,參數是什麼要根據第一個then中回調的返回值來判斷,返回值若是是正常值,若是是Piomise,若是報錯處理都不同,因此咱們封裝一個resolvePromise的方法來處理promise

須要改變的核心代碼以下
let resolvePromise = (promise2, x, resolve, reject) => {...}

class Promise {
    construcotr(){...}
    then(){
        let promise2 =  new promise((resolve,reject)=>{
            let x = onFulfiled() // onFulfilef是第一個then中的回調函數
            resolvePromise(promise2,x, resolve, reject)
        })
        return promiese2
    }
}

resolvePromise這個方法會判斷onFulfiled返回值類型,若是是普通值會怎麼樣,若是是一個Promise會怎麼樣,若是報錯會怎麼樣,詳細實現方法能夠參考promise A+規範
完整實現異步

let resolvePromise = (promise2, x, resolve, reject) => {
    // 監測到環形鏈
    if(promise2===x) return new TypeError('chaining cycle detected for promise')
    if(typeof x ==='function' ||(typeof x ==='object' && x!==null)){
        try{
            //嘗試取出then,有問題報錯
            let then = x.then
            if(typeof then === 'function'){ //這裏是最繞的,想清楚promise2和x的關係,x.then會不會執行取決於使用者的邏輯,會不會在第一個then中回調函數中返回的promise中調用它的resolve改變狀態
                then.call(x,resolve,reject)
            }else{// then不是function
                resolve(x)
            }
        }catch (e) {
            reject(e)
        }
    }else{ //普通類型
        resolve(x)
    }

}

class Promise {
    constructor(executor) {
        this.status = 'pending'
        this.value = undefined
        this.reason = undefined
        this.onFulfilledCallback = []
        this.onRejectedCallback = []
        let resolve = (value) => {
            if (this.status === 'pending') {
                this.status = 'fulfilled'
                this.value = value
                this.onFulfilledCallback.forEach(fn => fn(this.value))
            }

        }
        let reject = (reason) => {
            if (this.status === 'pending') {
                this.status = 'rejected'
                this.reason = reason
                this.onRejectedCallback.forEach(fn => fn(this.reason))
            }
        }
        try {
            executor(resolve, reject)
        } catch (e) {
            reject(e)
        }

    }

    then(onFulfilled, onRejected) { // 若是then的時候 根據當前狀態執行成功或者失敗
        let promise2 = new Promise((resolve, reject) => {
            if (this.status === 'fulfilled') {
                setTimeout(() => { //這裏之因此異步是由於必須保證resolvePromise(promise2, x, resolve, reject)時Promise2建立完成
                    try {
                        let x = onFulfilled(this.value)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                })
            }
            if (this.status === 'rejected') {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                })
            }
            if (this.status === 'pending') {
                this.onFulfilledCallback.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e)
                        }
                    })

                })
                this.onRejectedCallback.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e)
                        }
                    })
                })
            }
        })
        return promise2
    }

}

基本面試5-10分鐘代碼寫到這裏,都能給滿分經過,剩下的就是4個打補丁的地方了函數

4.打補丁

4.1 補丁點

其實是A+規範測試用例的補丁,我按重要程度往下排,前面的必須作到能寫出來(面試能夠不寫),後面的知道便可測試

  1. then的默認參數配置
  2. x多是個Promise,它的返回值還多是個Pormise,這個Promised的返回值還多是個Promise.....
  3. 調用Promise的resolve方法,若是參數是個promise怎麼辦 (這個不在A+規範裏,可是新版promise實現了)
  4. 別人實現的可能不規範,咱們的resolvePromise須要加一點限制,改變了狀態就不能再變了 (這個在A+規範測試用例裏,可是我感受意義不大)

4.1.1 默認參數

Promise.resolve(1).then().then().then().then((val)=>{
    console.log(val)        //1
})
//失敗也是相似的傳遞

能夠默認傳遞一個回調函數
then(onFufilled,onRejected){this

onFufilled = typeof onFufilled === 'function'?onFufilled:value=>value;
    ...

}

4.1.2 x中promise嵌套

這個也不難,遞歸調用resolvePromise去解析

let resolvePromise = (promise2,x,resolve,reject) => {
    ...
    then = x.then
    /*這個是以前的核心代碼 then.call(x,resolve,reject)  
    *實際等同於 then.call(x,(y)=>{
    *             resolve(y)   這個y是x做爲promise的返回值,如今這個y多是個promise因此再遞歸調用resolvePromise去解析
    *          },reject)  
    */
   改爲這樣:
   then.call(x,(y)=>{
       resolvePromise((promise2,y,resolve,reject)  
   },reject)
    ...
}

4.1.3 resolve中是promise

constructor(executor){
       ...
        let resolve = (value) =>{ // 若是resolve的值時一個promise
            if(value instanceof Promise){
                // 我就讓這個promise執行,把成功的結果再次判斷
                return value.then(resolve,reject) //參數會自動傳遞到resolve裏面去
            }
           
        }
        ...

5.添加方法

Promise比較重要的方法一共有五個方法

5.1 Promise.resovle

把一個對象包裝成Promise對象,特別注意狀態不必定是成功的
各類注意事項請看阮一峯es6-promise
直接記憶很差記憶,可是結合源碼很簡單,理所固然

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

5.2 Promise.reject

Promise.reject(reason)方法也會返回一個新的 Promise 實例,該實例的狀態爲rejected

static reject(err){
       return new Promise((resolve,reject)=>{
           reject(err);
       })
   }

5.3 PromiseInstance.prototype.finally

這個是實例方法,其餘幾個都是類方法
不管成功仍是失敗都會調用,因此可定返回的也是一個Promimse,成功失敗都會調用傳入的回調,
finally不接受值,返回的Promise的狀態受前一個promise狀態的影響
finally若是在中間同時回調返回一個promise則會等待promise

Promise.resolve(1).finally( 
    (a)=>{
        return new Promise((resolve)=>{
            setTimeout(function () {
                       resolve(2)
                    },3000)
                })
        
    }
).then((data)=>{
    console.log(data)
})

等待3秒後打印1

finally實現

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

5.4 Promise.race Promise.all

race和all一個是誰先調用誰執行後面then中的回調,一個是所有調用才執行後面then中的回調
他們都須要對參數中傳入的數組進行遍歷

all的實現須要藉助計數器,這也是實現異步任務通知的一種方法
直接完成或者異步完成都會使計數器加1 當計數器和數組長度相等時就是all方法完成的時候,而後把結果數組傳到下一個回調

race的實現就是,遍歷數組中元素current,都去改變返回promise的值,誰先改變就取誰的值傳到會帶哦函數裏面

return promose((resolve,reject)=>{
if(isPromise(current)){
                current.then(resolve,reject)
            }else{
                resolve(current)
            }
})

具體實現見6

6.完整實現

let resolvePromise = (promise2,x,resolve,reject) => {
    if(promise2 === x){
        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
    }
    // 若是調用了失敗 就不能再調用成功 調用成功也不能再調用失敗
    let called;
    if(typeof x ==='function'  || (typeof x === 'object' && x!== null) ){
        try{
            let then = x.then; // Object,dedefineProperty
            if(typeof then === 'function'){
                then.call(x,(y)=>{   // x.then(y=>,err=>)
                    if(called) return;
                    called = true
                    // y有可能解析出來的仍是一個promise
                    // 在去調用resolvePromise方法 遞歸解析的過程
                    // resolve(y)
                    resolvePromise(promise2,y,resolve,reject); // 總有y是普通值的時候
                },e=>{
                    if(called) return;
                    called = true
                    reject(e);
                })
            }else{ 
                 if(called) return;
                 called = true
                resolve(x);
            }
        }catch(e){
            if(called) return;
            called = true
            reject(e);
        }
    }else{
         if(called) return;
        called = true
        resolve(x);  // '123'  123
    }
}
class Promise{
    constructor(executor){
        this.value = undefined;
        this.reason = undefined;
        this.status = 'pending';
        this.onResolvedCallbacks = [];
        this.onRejectedCallbacks = [];
        let resolve = (value) =>{ // 若是resolve的值時一個promise
            // if(typeof value === 'function' || (typeof value == 'object'&&value !== null)){
            //     if(typeof value.then == 'function'){
            //         return value.then(resolve,reject)
            //     }
            // }
            if(value instanceof Promise){
                // 我就讓這個promise執行,把成功的結果再次判斷
                return value.then(resolve,reject) //參數會自動傳遞到resolve裏面去
            }
            if(this.status === 'pending'){
                this.status = 'fulfilled'
                this.value = value;
                this.onResolvedCallbacks.forEach(fn=>fn());
            }
        }
        let reject = (reason) =>{
            if(this.status === 'pending'){
                this.status = 'rejected'
                this.reason = reason;
                this.onRejectedCallbacks.forEach(fn=>fn());
            }
        }
        try{
            executor(resolve,reject);
        }catch(e){
            console.log(e)
            reject(e);
        }
    }
    then(onFufilled,onRejected){
        // 可選參數的配置
        onFufilled = typeof onFufilled === 'function'?onFufilled:value=>value;
        onRejected = typeof onRejected === 'function'?onRejected:err=>{throw err}
        let promise2 = new Promise((resolve,reject)=>{
            if(this.status === 'fulfilled'){
                setTimeout(()=>{ // 爲了保證promise2 已經產生了
                    try{
                        let x = onFufilled(this.value);
                        resolvePromise(promise2,x,resolve,reject);
                    }catch(e){
                        console.log(e);
                        reject(e);
                    }
                })
            }
            if(this.status === 'rejected'){
                setTimeout(() => {
                    try{
                        let x= onRejected(this.reason);
                        resolvePromise(promise2,x,resolve,reject);
                    }catch(e){
                        reject(e);
                    }
                });
            }
            if(this.status === 'pending'){
                this.onResolvedCallbacks.push(()=>{
                    setTimeout(() => {
                        try{
                            let x = onFufilled(this.value);
                            resolvePromise(promise2,x,resolve,reject);
                        }catch(e){
                            reject(e);
                        }
                    })
                });
                this.onRejectedCallbacks.push(()=>{
                    setTimeout(() => {
                        try{
                            let x= onRejected(this.reason);
                            resolvePromise(promise2,x,resolve,reject);
                        }catch(e){
                            reject(e);
                        }
                    });
                });
            }
        })
        return promise2
    }
    finally(callback){
        let P = this.constructor;
        return this.then(
            value  => P.resolve(callback()).then(() => value),
            reason => P.resolve(callback()).then(() => { throw reason })
          );
    }
    catch(errCallback){ // catch是then的一個別名而已
        return this.then(null,errCallback)
    }
    static resolve(value){
        return new Promise((resolve,reject)=>{
            resolve(value);
        })
    }
    static reject(err){
        return new Promise((resolve,reject)=>{
            reject(err);
        })
    }
    
    static race(values){
        return new Promise((resolve,reject)=>{
        for(let i = 0 ; i<values.length;i++){
            let current = values[i];
            if(isPromise(current)){
                current.then(resolve,reject)
            }else{
                resolve(current)
            }
        }
    })
    }
    static all(values){
        return new Promise((resolve,reject)=>{
                let arr = []; // 最終的結果
                let i = 0;
                function processData(key,val) {
                    arr[key] = val; 
                    if(++i == values.length){
                        resolve(arr);
                    }
                }
                for(let i = 0 ; i<values.length;i++){
                    let current = values[i];
                    if(isPromise(current)){
                        current.then(y=>{
                            processData(i,y);
                        },reject)
                    }else{
                        processData(i,current);
                    }
                }
            })

    }
}
Promise.deferred = () => { // 測試方法
    let dfd = {};
    dfd.promise = new Promise((resolve,reject)=>{
        dfd.resolve = resolve;
        dfd.reject = reject;
    })
    return dfd; // 能夠檢測這個對象上的promise屬性 resolve方法 reject方法
}
module.exports = Promise;


// 全局安裝 只能在命令中使用  sudo npm install promises-aplus-tests -g
// promises-aplus-tests promise.js
// 本地安裝 能夠在命令下 和 咱們的代碼中使用

7.面試題

7.1 請寫出下面代碼運行結果

Promise.reject(1).then().finally( 
    (a)=>{
        console.log('a:'a) //undefined
        setTimeout(function () {
            console.log(2)
        },3000)
    }
).then((data)=>{
    console.log(3)
    console.log(data)
},(e)=>{
    console.log('error'+e) //打印error1
})

//

答案:a:undefined error1 過兩秒 2

7.2 promise構造器是同步仍是異步,then方法呢

來源:微醫
答案:同步,異步 源碼裏面寫的很清楚

7.3 模擬實現一個Promise.finally

答案:

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

7.4 介紹一下Promose.all的使用,原理及錯誤處理

使用:須要同時獲取多個東西后再執行回調
原理:返回一個Promise: p 遍歷參數數組,若不是promise,直接加入到結果數組arr中 計數器++
若是是Promise,等Promise執行完再講結果加到加過數組 計數器++
計數器===數組長度時證實所有完成,p.resolve(結果數組arr)
錯誤處理: p.reject(e)

7.5 設計並實現Promise.race

答案:

Promise._race = promises => new Promise((resolve, reject) => {
    promises.forEach(promise => {
        promise.then(resolve, reject)
    })
})

8 總結

總結了Promise的實現,以及面試常見考點,相信若是所有理解了,面試再問promise確定能夠加分很多。因爲技術有限,若是閱讀中發現有什麼錯誤,請在留言指出

相關文章
相關標籤/搜索