手寫實現(xiàn)簡單的promise

手寫promise幾乎每家大廠的必備考題翻具,幾次面試都被這個問題坑了峰锁,于是花了些時間特意研究了一下,下面是promise實現(xiàn)的思考過程坑填。大家如果不嫌棄抛人,還請往下看:

promise的介紹

所謂Promise,簡單說就是一個容器脐瑰,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果妖枚。從語法上說,Promise 是一個對象蚪黑,從它可以獲取異步操作的消息盅惜。
例如以下一個promise的例子:

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 異步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

可以看出一個promise的構造函數(shù)包含兩個方法resolve中剩、reject,同時根據(jù)promise+規(guī)范可知promise包含三個狀態(tài):

  • pending: 初始狀態(tài),既不是成功抒寂,也不是失敗狀態(tài)结啼。
  • fulfilled: 意味著操作成功完成。
  • rejected: 意味著操作失敗屈芜。
    那么我們可以可以根據(jù)這三種不同狀態(tài)去實現(xiàn)resolve郊愧、reject,以及實現(xiàn)then方法井佑,那么一個簡單的promise雛形就出來了属铁。下面來實現(xiàn)它:

promise構造函數(shù):

首先可以根據(jù)上面的推測寫個構造函數(shù)如下:

/**
 * 創(chuàng)建三變量記錄表示狀態(tài)
 * 用that保存this,避免后期閉包導致this的指向不對
 * value 變量用于保存 resolve 或者 reject 中傳入的值
 * resolvedCallbacks 和 rejectedCallbacks 用于保存 then 中的回調躬翁,
 * 因為當執(zhí)行完 Promise 時狀態(tài)可能還是等待中焦蘑,這時候應該把 then 中的回調保存起來用于狀態(tài)改變時使用
 */

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function myPromise(fn){
  const that = this;
  that.value = null;
  that.status = PENDING; //默認狀態(tài)
  that.fulfilledCallbacks = [];
  that.rejectedCallbacks = [];
  function resolve(value){
    if(that.status === PENDING ){
      }
  }
 function reject(value){
    if(that.status === PENDING ){
      }
  }
    
  // 執(zhí)行回調函數(shù)
   try{
        fn(resolve, reject)
    }catch (e) {
        reject(e);
    }
}

于是思考當在resolve里該干些什么?resolve即執(zhí)行狀態(tài)盒发,首先status狀態(tài)值得變吧例嘱,改成fulfilled狀態(tài),同時將傳入的value值保存起來宁舰,以便下面的then會用到拼卵,最后得執(zhí)行回調里面的方法實現(xiàn)回調調用。reject同理蛮艰,于是按這個思路腋腮,就有了:

/**
 * 創(chuàng)建三變量記錄表示狀態(tài)
 * 用that保存this,避免后期閉包導致this的指向不對
 * value 變量用于保存 resolve 或者 reject 中傳入的值
 * resolvedCallbacks 和 rejectedCallbacks 用于保存 then 中的回調壤蚜,
 * 因為當執(zhí)行完 Promise 時狀態(tài)可能還是等待中即寡,這時候應該把 then 中的回調保存起來用于狀態(tài)改變時使用
 */

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function myPromise(fn){
  const that = this;
  that.value = null;
  that.status = PENDING; //默認狀態(tài)
  that.fulfilledCallbacks = [];
  that.rejectedCallbacks = [];
  function resolve(value){
    if(that.status === PENDING ){
        that.status = FULFILLED;
        that.value = value;
        //執(zhí)行回調方法
        that.fulfilledCallbacks.forEach(myFn => myFn(that.value))
      }
  }
 function reject(value){
    if(that.status === PENDING ){
        that.status = REJECTED;
        that.value = value;
        //執(zhí)行回調方法
        that.rejectedCallbacks.forEach(myFn => myFn(that.value))
      }
  }
    
  // 執(zhí)行回調函數(shù)
   try{
        fn(resolve, reject)
    }catch (e) {
        reject(e);
    }
}

于是promise的構造函數(shù)就簡陋的完成了,接下來實現(xiàn)then不就大工完成了嗎仍律?是不是有些小興奮~~~

promise中then的實現(xiàn)

考慮到所有的實例都要用到then方法嘿悬,在then得放在promise的原型鏈上实柠。當狀態(tài)是PENDING狀態(tài)時水泉,該做什么?不執(zhí)行回調窒盐,那就將回調方法分別放入不同的棧內草则,等待調用。當狀態(tài)為FULFILLED或者REJECTED時蟹漓,則執(zhí)行響應的方法即可炕横。于是:

   myPromise.prototype.then = function (onFulfilled, onRejected){
    let self = this;
    //等待狀態(tài),則添加回調函數(shù)到棧中
    if(self.status === PENDING){
        self.fulfilledCallbacks.push(()=>{
            onFulfilled(self.value);
        });
        self.rejectedCallbacks.push(()=>{
            onRejected(self.value);
        })
    }
    if(self.status === FULFILLED){
        onFulfilled(self.value);
    }

    if(self.status === REJECTED){
        onRejected(self.value)
    }
}

于是一個簡單的promise實現(xiàn)如下:

/**
 * 創(chuàng)建三變量記錄表示狀態(tài)
 * 用that保存this葡粒,避免后期閉包導致this的指向不對
 * value 變量用于保存 resolve 或者 reject 中傳入的值
 * resolvedCallbacks 和 rejectedCallbacks 用于保存 then 中的回調份殿,
 * 因為當執(zhí)行完 Promise 時狀態(tài)可能還是等待中膜钓,這時候應該把 then 中的回調保存起來用于狀態(tài)改變時使用
 */

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function myPromise(fn){
  const that = this;
  that.value = null;
  that.status = PENDING; //默認狀態(tài)
  that.fulfilledCallbacks = [];
  that.rejectedCallbacks = [];
  function resolve(value){
    if(that.status === PENDING ){
        that.status = FULFILLED;
        that.value = value;
        //執(zhí)行回調方法
        that.fulfilledCallbacks.forEach(myFn => myFn(that.value))
      }
  }
 function reject(value){
    if(that.status === PENDING ){
        that.status = REJECTED;
        that.value = value;
        //執(zhí)行回調方法
        that.rejectedCallbacks.forEach(myFn => myFn(that.value))
      }
  }
    
  // 執(zhí)行回調函數(shù)
   try{
        fn(resolve, reject)
    }catch (e) {
        reject(e);
    }
}
 myPromise.prototype.then = function (onFulfilled, onRejected){
    let self = this;
    //等待狀態(tài),則添加回調函數(shù)到棧中
    if(self.status === PENDING){
        self.fulfilledCallbacks.push(()=>{
            onFulfilled(self.value);
        });
        self.rejectedCallbacks.push(()=>{
            onRejected(self.value);
        })
    }
    if(self.status === FULFILLED){
        onFulfilled(self.value);
    }

    if(self.status === REJECTED){
        onRejected(self.value)
    }
}

let p = new myPromise((resolve, reject)=>{
    console.log('hello');
    resolve(5);
});
p.then((res)=>{
    console.log(res);
})
p.then(()=>{
    console.log('jj');
})

結果如下:


image.png

于是一個簡單的promise大工告成卿嘲!

可是有沒有發(fā)現(xiàn)then里并沒有返回一個promise,并不符合規(guī)范颂斜,所以可以對promise進行部分優(yōu)化。

改造promise

根據(jù)promise+規(guī)范改造promise拾枣,傳給then的應該是個promise,如下:
resolve 和reject的改造:

  • 對于 resolve 函數(shù)來說沃疮,首先需要判斷傳入的值是否為 Promise 類型
  • 為了保證函數(shù)執(zhí)行順序,需要將兩個函數(shù)體代碼使用 setTimeout 包裹起來
 function resolve(value) {
        if(value instanceof myPromise){
            return value.then(resolve, reject);
        }
        setTimeout(()=>{
            that.status = FULFILLED;
            that.value = value;
            //執(zhí)行會回調方法
            that.fulfilledCallbacks.forEach(myFn => myFn(that.value))
        },0)
    }

    function reject(value) {
        setTimeout(()=>{
            that.status = REJECTED;
            that.value = value;
            //執(zhí)行會回調方法
            that.rejectedCallbacks.forEach(myFn => myFn(that.value))
        }, 0);
    }

接下來改造then方法:
首先我們需要新增一個變量 promise2梅肤,因為每個 then 函數(shù)都需要返回一個新的 Promise 對象司蔬,該變量用于保存新的返回對象,于是:

myPromise.prototype.then = function (onFulfilled, onRejected) {
    let self = this;
    let promise2 = null;
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
    onRejected = typeof onRejected === 'function' ? onRejected : r => {throw r}
    //等待狀態(tài)姨蝴,則添加回調函數(shù)到棧中
    if(self.status === PENDING){
        return (promise2 = new myPromise((resolve, reject)=>{
            self.fulfilledCallbacks.push(()=>{
                try {
                    let x = onFulfilled(self.value);
                    resolutionProduce(promise2, x, resolve, reject);
                }catch (e) {
                    reject(e)
                }
            });
            self.rejectedCallbacks.push(()=>{
                try {
                    let x = onRejected(self.value);
                    resolutionProduce(promise2, x, resolve, reject);
                }catch (e) {
                    reject(e);
                }
                onRejected(self.value);
            });
        }));
    }
    if(self.status === FULFILLED){
        return(promise2 = new myPromise((resolve, reject)=>{
            setTimeout(()=>{
                try {
                    let x = onFulfilled(self.value);
                    resolutionProduce(promise2, x, resolve, reject);
                }catch (e) {
                    reject(e);
                }
            }, 0)
        }));
    }

    if(self.status === REJECTED){
        return (promise2 = new myPromise((resolve, reject)=>{
            setTimeout(()=>{
                try {
                    let x = onRejected(self.value);
                    resolutionProduce(promise2, x, resolve, reject)
                }catch (e) {
                    reject(e);
                }
            },0)
        }));
    }
}

思路跟之前基本一致俊啼,只是說把之前返回值改成promise,同時捕獲異常,在status狀態(tài)為FULFILLED或者REJECTED的時候執(zhí)行得加上異步setTimeout包裹左医。
接下來完成最核心的resolutionProduce函數(shù):

  • 首先規(guī)范規(guī)定得保證當前的x不能與promise2一致吨些,否則將執(zhí)行無意義的相同操作,導致循環(huán)引用的發(fā)生炒辉。
    例如:
let p = new myPromise((resolve, reject) => {
  resolve(1)
})
let p1 = p.then(value => {
  return p1
})
  • 然后需要判斷 x 的類型
if (x instanceof MyPromise) {
    x.then(function(value) {
        resolutionProcedure(promise2, value, resolve, reject)
    }, reject)
}

這里的代碼是完全按照規(guī)范實現(xiàn)的豪墅。如果 x 為 Promise 的話,需要判斷以下幾個情況:

  1. 如果 x 處于等待態(tài)黔寇,Promise 需保持為等待態(tài)直至 x 被執(zhí)行或拒絕
  2. 如果 x 處于其他狀態(tài)偶器,則用相同的值處理 Promise
function resolutionProduce(promise, x, resolve, reject){
 if(promise === x){
        return reject(new TypeError('Error'));
 }
 let called = false;
 if(x !== null && (typeof x === 'object' || typeof x === 'function')){
        try {
            let then = x.then;
            if(typeof then === 'function'){
                then.call(x, y=>{
                    if(called) return;
                    called = true;
                    resolutionProduce(promise2, y, resolve, reject )
                }, e =>{
                    if(e) return;
                    called = true;
                    reject(e);
                })
            }else {
                resolve(x);
            }
        }catch (e) {
            if(called) return;
            called = true;
            reject(e);
        }
    }else {
        resolve(x);
    }
}
  • 首先創(chuàng)建一個變量 called 用于判斷是否已經(jīng)調用過函數(shù)
  • 然后判斷 x 是否為對象或者函數(shù),如果都不是的話缝裤,將 x 傳入 resolve 中
  • 如果 x 是對象或者函數(shù)的話屏轰,先把 x.then 賦值給 then,然后判斷 then 的類型憋飞,如果不是函數(shù)類型的話霎苗,就將 x 傳入 resolve 中
  • 如果 then 是函數(shù)類型的話,就將 x 作為函數(shù)的作用域 this 調用之榛做,并且傳遞兩個回調函數(shù)作為參數(shù)唁盏,第一個參數(shù)叫做 resolvePromise ,第二個參數(shù)叫做
    rejectPromise检眯,兩個回調函數(shù)都需要判斷是否已經(jīng)執(zhí)行過函數(shù)厘擂,然后進行相應的邏輯
  • 以上代碼在執(zhí)行的過程中如果拋錯了,將錯誤傳入 reject 函數(shù)中

以上便是promise的簡單實現(xiàn)锰瘸,下回有時間將把catch與all刽严、race等方法實現(xiàn),敬請期待
未完待續(xù)避凝。舞萄。眨补。
參考: https://juejin.im/post/5b88e06451882542d733767a

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市倒脓,隨后出現(xiàn)的幾起案子渤涌,更是在濱河造成了極大的恐慌,老刑警劉巖把还,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件实蓬,死亡現(xiàn)場離奇詭異,居然都是意外死亡吊履,警方通過查閱死者的電腦和手機安皱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來艇炎,“玉大人酌伊,你說我怎么就攤上這事∽鹤伲” “怎么了居砖?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長驴娃。 經(jīng)常有香客問我奏候,道長,這世上最難降的妖魔是什么唇敞? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任蔗草,我火速辦了婚禮,結果婚禮上疆柔,老公的妹妹穿的比我還像新娘咒精。我一直安慰自己,他們只是感情好旷档,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布模叙。 她就那樣靜靜地躺著,像睡著了一般鞋屈。 火紅的嫁衣襯著肌膚如雪范咨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天谐区,我揣著相機與錄音湖蜕,去河邊找鬼逻卖。 笑死宋列,一個胖子當著我的面吹牛,可吹牛的內容都是我干的评也。 我是一名探鬼主播炼杖,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼灭返,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了坤邪?” 一聲冷哼從身側響起熙含,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎艇纺,沒想到半個月后怎静,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡黔衡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年蚓聘,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盟劫。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡夜牡,死狀恐怖,靈堂內的尸體忽然破棺而出侣签,到底是詐尸還是另有隱情塘装,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布影所,位于F島的核電站蹦肴,受9級特大地震影響,放射性物質發(fā)生泄漏猴娩。R本人自食惡果不足惜冗尤,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望胀溺。 院中可真熱鬧裂七,春花似錦、人聲如沸仓坞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽无埃。三九已至徙瓶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嫉称,已是汗流浹背侦镇。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留织阅,地道東北人壳繁。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親闹炉。 傳聞我的和親對象是個殘疾皇子蒿赢,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354