不積跬步之手寫Promise(上)

promise.jpeg

我們從最簡(jiǎn)單的切入口開始恳守。

1.整體結(jié)構(gòu)的搭建

要實(shí)現(xiàn)的官方的功能

let p = new Promise((resolve,reject)=>{
    resolve("ok");
});

p.then(value=>{
    console.log(value);
},reason => {
    console.log(reason)
});

觀察上面的結(jié)構(gòu):

  1. Promise構(gòu)造函數(shù)有一個(gè)回調(diào)函數(shù) 我們就給起名字為executor
  2. 實(shí)例對(duì)象上面有個(gè)then方法
  3. then方法有兩個(gè)回調(diào)函數(shù) onResolved,onRejected

為了和Promise做區(qū)分,這里起名字為PromiseA目標(biāo)就明朗了,我們的實(shí)現(xiàn)版本如下:

function PromiseA(executor){

}

PromiseA.prototype.then = function (onResolved,onRejected){

}

測(cè)試用例:

let p1 = new PromiseA((resolve,reject)=>{
    resolve("ok");
});

p1.then(value=>{
    console.log(value);
},reason => {
    console.log(reason)
});
//沒有報(bào)錯(cuò),說明實(shí)現(xiàn)了我們最初的目的

2.resolvereject的搭建

let p = new Promise((resolve,reject)=>{
    resolve("ok");
});

p.then(value=>{
    console.log(value);
},reason => {
    console.log(reason)
});

2.1 還是觀察官方的例子,發(fā)現(xiàn)構(gòu)造函數(shù)中的回調(diào)函數(shù)中

構(gòu)造函數(shù)中的是同步調(diào)用的 怎么同步調(diào)用呢? 我們直接在構(gòu)造函數(shù)里面把回調(diào)函數(shù)exector調(diào)用一下就可以

function PromiseA(executor){
    //同步調(diào)用一下
    executor();
}

2.2 同步調(diào)用以后發(fā)現(xiàn),回調(diào)函數(shù)里面還有兩個(gè)回調(diào)的參數(shù).resolvereject.那么這兩個(gè)參數(shù)是哪里來的呢?他們是什么?

通過觀察發(fā)現(xiàn) resolve("ok").原來它們都是函數(shù),同時(shí)這個(gè)函數(shù)里面還有 調(diào)用的時(shí)候傳入的參數(shù).好,安排.

function PromiseA(executor){
    function resolve(data){
    
    }
    
    function reject(data){
    
    }
    //同步調(diào)用一下
    executor(resolve,reject);
}

3.resolvereject的實(shí)現(xiàn)

在之前的研究中我們知道resolve("OK")被調(diào)用的時(shí)候,它會(huì)有以下兩個(gè)作用:

  • 修改實(shí)例對(duì)象Promise的狀態(tài)為fulfilled.
  • 它傳入的參數(shù)ok就是結(jié)果值

所以我們需要實(shí)現(xiàn)這兩步:

  1. 修改狀態(tài)---那么就需要一個(gè)初始狀態(tài)
  2. 改變結(jié)果值---那么就需要一個(gè)存儲(chǔ)結(jié)果值的參數(shù)

我們看一下官網(wǎng)Promise的內(nèi)部狀態(tài)

[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: "OK"

可以看到它內(nèi)部有兩個(gè)內(nèi)部屬性:PromiseStatePromiseResult.那我們就知道了怎么做了.

function PromiseA(executor){
    //添加屬性
    this.PromiseState = "pending";
    this.PromiseResult = null;
    //resolve函數(shù)
    function resolve(data){
        //1.修改對(duì)象的狀態(tài)(PromiseState)
        this.PromiseState = "fulfilled";
        //2.設(shè)置對(duì)象的結(jié)果值(PromiseResult)
        this.PromiseResult = data;
    }
    
    function reject(data){
    
    }
    //同步調(diào)用一下
    executor(resolve,reject);
}

這里有一個(gè)問題,我們調(diào)用上面的實(shí)例會(huì)發(fā)現(xiàn):

let p1 = new PromiseA((resolve,reject)=>{
    resolve("ok");
});
console.log(p1)
// PromiseA { PromiseState: 'pending', PromiseResult: null }

我們的修改這里并沒有生效,這是因?yàn)楹瘮?shù)在單獨(dú)的環(huán)境中調(diào)用,它是指向window的.所以我們這里可以借助詞法作用域的特性來解決這個(gè)問題. 通過設(shè)定一個(gè)selt來保存this的值

function PromiseA(executor){
    //添加屬性
    this.PromiseState = "pending";
    this.PromiseResult = null;
    //預(yù)先保存實(shí)例對(duì)象的this值
    const self = this;
    
    //resolve函數(shù)
    function resolve(data){
        //1.修改對(duì)象的狀態(tài)(PromiseState)
        self.PromiseState = "fulfilled";
        //2.設(shè)置對(duì)象的結(jié)果值(PromiseResult)
        self.PromiseResult = data;
    }
    
    function reject(data){
        //1.修改對(duì)象的狀態(tài)(PromiseState)
        self.PromiseState = "rejected";
        //2.設(shè)置對(duì)象的結(jié)果值(PromiseResult)
        self.PromiseResult = data;
    }
    //同步調(diào)用一下
    executor(resolve,reject);
}

reject同理 ,就實(shí)現(xiàn)了我們上面的這一版

4. throw 拋出異常改變狀態(tài)

這一節(jié)我們實(shí)現(xiàn)的狀態(tài)是 throw拋出異常會(huì)改變狀態(tài).

let p1 = new Promise((resolve,reject)=>{
    throw "error"
});

console.log(p1)

 Promise {<rejected>: 'error'}
 [[Prototype]]: Promise
 [[PromiseState]]: "rejected"
 [[PromiseResult]]: "error"

可以看到官放的Promise 在其內(nèi)部報(bào)錯(cuò)的時(shí)候,它會(huì)把內(nèi)部的throw處理掉,同時(shí)把狀態(tài)修改,
把錯(cuò)誤的結(jié)果放到PromiseResult中.

function PromiseA(executor){
    //添加屬性
    this.PromiseState = "pending";
    this.PromiseResult = null;
    //預(yù)先保存實(shí)例對(duì)象的this值
    const self = this;
    
    //resolve函數(shù)
    function resolve(data){
        //...
    }
    function reject(data){
       //...
    }
    //-------------- 修改的代碼-----------------
    try{
        //同步調(diào)用一下
        executor(resolve,reject);
    }catch (e){
        //通過調(diào)用reject函數(shù),它的內(nèi)部可以修改狀態(tài)的和賦值
        //所以我們這里可以把錯(cuò)誤直接傳進(jìn)去就可以了.
        reject(e);
    }
    //-------------- 修改的代碼-----------------
}

既然要處理錯(cuò)誤,我們就添加一個(gè)try catch 然后通過reject 來處理內(nèi)部的狀態(tài)和數(shù)據(jù).

看一下測(cè)試用例的輸出:

let p1 = new PromiseA((resolve,reject)=>{
    throw "error"
});

console.log(p1)

//PromiseA { PromiseState: 'rejected', PromiseResult: 'error' }

5.內(nèi)部的狀態(tài)只能修改一次

內(nèi)部狀態(tài)只能修改一次,就是說 我們先調(diào)用了resolve,然后調(diào)用reject,并不能把狀態(tài)fulfilled修改為了rejected.

那么怎么實(shí)現(xiàn)呢? 只需要添加個(gè)判斷就好

function PromiseA(executor){
    //添加屬性
    this.PromiseState = "pending";
    this.PromiseResult = null;
    //預(yù)先保存實(shí)例對(duì)象的this值
    const self = this;
    
    //resolve函數(shù)
    function resolve(data){
        //-------------- 修改的代碼-----------------
        //判斷狀態(tài)
        if(self.PromiseState !== "pending"){
            return ;
        }
        //-------------- 修改的代碼-----------------
        //1.修改對(duì)象的狀態(tài)(PromiseState)
        self.PromiseState = "fulfilled";
        //2.設(shè)置對(duì)象的結(jié)果值(PromiseResult)
        self.PromiseResult = data;
    }
    
    function reject(data){
        //-------------- 修改的代碼-----------------
        //判斷狀態(tài)
        if(self.PromiseState !== "pending"){
            return ;
        }
        //-------------- 修改的代碼-----------------
        //1.修改對(duì)象的狀態(tài)(PromiseState)
        self.PromiseState = "rejected";
        //2.設(shè)置對(duì)象的結(jié)果值(PromiseResult)
        self.PromiseResult = data;
    }
    
    //處理 throw 拋出的錯(cuò)誤
    try{
        //同步調(diào)用一下
        executor(resolve,reject);
    }catch (e){
        //通過調(diào)用reject函數(shù),它的內(nèi)部可以修改狀態(tài)的和賦值
        //所以我們這里可以把錯(cuò)誤直接傳進(jìn)去就可以了.
        reject(e);
    }
}

測(cè)試用例: 可以看到我們的狀態(tài)并沒有被再次改變?yōu)?code>rejected

let p1 = new PromiseA((resolve,reject)=>{
    resolve("ok");
    reject("error");
});
console.log(p1)

//PromiseA { PromiseState: 'fulfilled', PromiseResult: 'ok' }

6.then方法的實(shí)現(xiàn)

官方的then方法接收兩個(gè)函數(shù)的回調(diào)參數(shù),分別對(duì)應(yīng)于成功的情況和失敗的情況.
當(dāng)new Promise的構(gòu)造函數(shù)中調(diào)用resolve,那么對(duì)應(yīng)的then方法中就會(huì)執(zhí)行第一個(gè)回調(diào)函數(shù).
當(dāng)new Promise的構(gòu)造函數(shù)中調(diào)用reject,那么對(duì)應(yīng)的then方法中就會(huì)執(zhí)行第二個(gè)回調(diào)函數(shù).

let p = new Promise((resolve,reject)=>{
    throw "error"
});
p.then(value=>{
    console.log(value)
},reason => {
    console.log(reason)
})

所以我們的then方法的兩個(gè)形參 對(duì)應(yīng)的 Promise的兩個(gè)實(shí)參. onResolvedonRejected

//添加 then 方法
PromiseA.prototype.then = function (onResolved,onRejected){
   
}

既然構(gòu)造函數(shù) 執(zhí)行resolve ,就會(huì)調(diào)用onResolved,所以需要在then方法中進(jìn)行調(diào)用.

//添加 then 方法
PromiseA.prototype.then = function (onResolved,onRejected){
    //調(diào)用 成功 回調(diào)函數(shù)
    onResolved();
    //調(diào)用失敗的回調(diào)函數(shù)
    onRejected();
}

但是這樣調(diào)用的話,且不是兩個(gè)都調(diào)用了,我們需要區(qū)分情況來對(duì)待.成功的時(shí)候調(diào)用onResolved,
失敗的時(shí)候調(diào)用onRejected.所以應(yīng)該怎么區(qū)分情況呢?

//添加 then 方法
PromiseA.prototype.then = function (onResolved,onRejected){
    //調(diào)用 成功 回調(diào)函數(shù)
    if(this.PromiseState === "fulfilled"){
        onResolved();
    }
    //調(diào)用失敗的回調(diào)函數(shù)
    if(this.PromiseState === "rejected"){
        onRejected();
    }
}

既然then方法是構(gòu)造函數(shù)執(zhí)行完畢返回實(shí)例之后,通過實(shí)例調(diào)用的.這里就有了隱式綁定this.
這個(gè)時(shí)候this綁定的是實(shí)例對(duì)象p.所以我們這個(gè)時(shí)候可以通過this,拿到實(shí)例對(duì)象中的狀態(tài)和數(shù)據(jù).
PromiseStatePromiseResult.

then方法的兩個(gè)函數(shù)回調(diào)中,會(huì)返回實(shí)例對(duì)象執(zhí)行結(jié)果的數(shù)據(jù),所以我們?cè)谡{(diào)用onResolvedonRejected的時(shí)候,給它們傳入實(shí)例的執(zhí)行結(jié)果 PromiseResult.

所以最后就是這樣:

//添加 then 方法
PromiseA.prototype.then = function (onResolved,onRejected){
    //調(diào)用 成功 回調(diào)函數(shù)
    if(this.PromiseState === "fulfilled"){
        onResolved(this.PromiseResult);
    }
    //調(diào)用失敗的回調(diào)函數(shù)
    if(this.PromiseState === "rejected"){
        onRejected(this.PromiseResult);
    }
}

7.異步任務(wù) then 方法實(shí)現(xiàn)

異步任務(wù)的實(shí)現(xiàn)就是:

let p = new PromiseA((resolve,reject)=>{
    //異步調(diào)用
    setTimeout(()=>{
        resolve("OK");
    },100)
});
console.log(p);

p.then(value=>{
    console.log(value)
},reason => {
    console.log(reason)
})

//PromiseA { PromiseState: 'pending', PromiseResult: null }

我們之前在構(gòu)造函數(shù)中都是同步調(diào)用resolve,在執(zhí)行的下面的then的時(shí)候,實(shí)際上狀態(tài)已經(jīng)修改為
fulfilled或者rejected.

而異步調(diào)用的話,可以看上面的打印輸出.實(shí)例的狀態(tài)是pending.它的狀態(tài)是在未來的某個(gè)時(shí)間進(jìn)行的改變.
而我們的then方法中,卻并沒有對(duì)pending狀態(tài)的處理,所以我們需要給它添加一下.

//添加 then 方法
PromiseA.prototype.then = function (onResolved,onRejected){
    //調(diào)用 成功 回調(diào)函數(shù)
    if(this.PromiseState === "fulfilled"){
        onResolved(this.PromiseResult);
    }
    //調(diào)用失敗的回調(diào)函數(shù)
    if(this.PromiseState === "rejected"){
        onRejected(this.PromiseResult);
    }
    
    //添加pending狀態(tài)的處理
    if(this.PromiseState === "pending"){
        //...
    }
}

那這里應(yīng)該怎么處理呢?
由于是異步調(diào)用的原因,當(dāng)同步執(zhí)行到then方法的時(shí)候,它的內(nèi)部狀態(tài)還是pending,所以給同步代碼使用的

 //調(diào)用 成功 回調(diào)函數(shù)
    if(this.PromiseState === "fulfilled"){
        onResolved(this.PromiseResult);
    }
    //調(diào)用失敗的回調(diào)函數(shù)
    if(this.PromiseState === "rejected"){
        onRejected(this.PromiseResult);
    }

就用不上了,而內(nèi)部狀態(tài)的改變是在未來的某個(gè)地方進(jìn)行的? 是在哪里呢?

function PromiseA(executor){
    //添加屬性
    //....
    //resolve函數(shù)
    function resolve(data){
        //判斷狀態(tài)
        if(self.PromiseState !== "pending"){
            return ;
        }
        //--------------- 在未來某個(gè)地方改變狀態(tài)的就是這里----------
        //1.修改對(duì)象的狀態(tài)(PromiseState)
        self.PromiseState = "fulfilled";
        //2.設(shè)置對(duì)象的結(jié)果值(PromiseResult)
        self.PromiseResult = data;
    }
    function reject(data){
        //判斷狀態(tài)
        //....
    }
    //處理 throw 拋出的錯(cuò)誤
    //...
}

所以我們需要在狀態(tài)改變的地方去調(diào)用then方法的回調(diào). ,也就是需要一個(gè)變量把then方法的兩個(gè)回調(diào)函數(shù)
保存起來,然后在構(gòu)造函數(shù)resolve的改變狀態(tài)的地方進(jìn)行調(diào)用.所以我們定義一個(gè)callback變量來存儲(chǔ)回調(diào)參數(shù).

function PromiseA(executor){
    //添加屬性
    this.PromiseState = "pending";
    this.PromiseResult = null;
     //-------------- 修改的代碼-----------------
    this.callback = {};
     //-------------- 修改的代碼-----------------
    //預(yù)先保存實(shí)例對(duì)象的this值
    const self = this;
    
    //resolve函數(shù)
    function resolve(data){
        //判斷狀態(tài)
        if(self.PromiseState !== "pending"){
            return ;
        }
        //1.修改對(duì)象的狀態(tài)(PromiseState)
        self.PromiseState = "fulfilled";
        //2.設(shè)置對(duì)象的結(jié)果值(PromiseResult)
        self.PromiseResult = data;
         //-------------- 修改的代碼-----------------
        //調(diào)用成功的回調(diào)函數(shù)
        if(self.callback.onResolved){
            //參數(shù)是成功的結(jié)果
            self.callback.onResolved(data);
        }
         //-------------- 修改的代碼-----------------
    }
    
    function reject(data){
        //判斷狀態(tài)
        // ...
    }
    
    //處理 throw 拋出的錯(cuò)誤
    //....
}

我們?cè)?code>then方法的中判斷pending來保存callback;

//添加 then 方法
PromiseA.prototype.then = function (onResolved,onRejected){
    //調(diào)用 成功 回調(diào)函數(shù)
    if(this.PromiseState === "fulfilled"){
        onResolved(this.PromiseResult);
    }
    //調(diào)用失敗的回調(diào)函數(shù)
    if(this.PromiseState === "rejected"){
        onRejected(this.PromiseResult);
    }
    //-------------- 修改的代碼-----------------
    //添加pending狀態(tài)的處理
    if(this.PromiseState === "pending"){
        //保存回調(diào)函數(shù)
        this.callback = {
            onResolved,
            onRejected
        };
    }
    //-------------- 修改的代碼-----------------
}

測(cè)試用例:

let p = new PromiseA((resolve,reject)=>{
    //異步調(diào)用
    setTimeout(()=>{
        resolve("OK");
    },100)
});
p.then(value=>{
    console.log(value)
},reason => {
    console.log(reason)
})
//輸出 OK
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末璃弄,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子袍嬉,更是在濱河造成了極大的恐慌,老刑警劉巖津畸,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異必怜,居然都是意外死亡肉拓,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門梳庆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來暖途,“玉大人,你說我怎么就攤上這事膏执∽な郏” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵更米,是天一觀的道長(zhǎng)欺栗。 經(jīng)常有香客問我,道長(zhǎng)征峦,這世上最難降的妖魔是什么迟几? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮栏笆,結(jié)果婚禮上瘤旨,老公的妹妹穿的比我還像新娘。我一直安慰自己竖伯,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布因宇。 她就那樣靜靜地躺著七婴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪察滑。 梳的紋絲不亂的頭發(fā)上打厘,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音贺辰,去河邊找鬼户盯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛饲化,可吹牛的內(nèi)容都是我干的莽鸭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼吃靠,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼硫眨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起巢块,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤礁阁,失蹤者是張志新(化名)和其女友劉穎巧号,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體姥闭,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡丹鸿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了棚品。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片靠欢。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖南片,靈堂內(nèi)的尸體忽然破棺而出掺涛,到底是詐尸還是另有隱情,我是刑警寧澤疼进,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布薪缆,位于F島的核電站,受9級(jí)特大地震影響伞广,放射性物質(zhì)發(fā)生泄漏拣帽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一嚼锄、第九天 我趴在偏房一處隱蔽的房頂上張望减拭。 院中可真熱鬧,春花似錦区丑、人聲如沸拧粪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽可霎。三九已至,卻和暖如春宴杀,著一層夾襖步出監(jiān)牢的瞬間癣朗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工旺罢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留旷余,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓扁达,卻偏偏與公主長(zhǎng)得像正卧,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子罩驻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容