動(dòng)手實(shí)現(xiàn)Promise

前言

之前已經(jīng)學(xué)習(xí)過(guò)了Promise的相關(guān)知識(shí),Promise其實(shí)就是一個(gè)容器,里面保存著某個(gè)未來(lái)才會(huì)結(jié)束的事件(通常是一個(gè)異步操作)的結(jié)果。一般的Promise長(zhǎng)這個(gè)樣子:

const promise = new Promise((resolve, reject) => {
    if(/* 一般是異步操作 */) {
        resolve('回調(diào)');
    } else {
        reject('error');
    } 
});

promise.then((data) => {
    console.log(data);
}).catch((error) => {
    console.log(error);
});

下面嘗試一下自己去實(shí)現(xiàn)Promise的功能。

基本功能

// 定一個(gè)構(gòu)造函數(shù)
function MyPromise(callBack: Function) {
  let thenFunc: ?Function = null;

  this.then = (call) => {
    thenFunc = call;
  };

  const thenCall = (value) => {
    thenFunc && thenFunc(value);
    thenFunc = null;
  };

  callBack(thenCall);
}

// 使用
const promise = new MyPromise((resolve) => {
    setTimeout(() => {
        resolve('ssss');
    }, 1000);
});

promise.then((data) => {
    console.log(data);
});

定義一個(gè)構(gòu)造函數(shù),將初始化的異步方法傳入其中。在函數(shù)內(nèi)部定一個(gè)變量來(lái)存儲(chǔ)promise實(shí)例傳遞進(jìn)來(lái)的then方法,當(dāng)異步方法開(kāi)始執(zhí)行的時(shí)候就會(huì)在函數(shù)內(nèi)部調(diào)用這個(gè)變量存儲(chǔ)的函數(shù),從將參數(shù)回調(diào)至promise實(shí)例中。

初始化的異步方法中,resolve參數(shù)其實(shí)是一個(gè)函數(shù),對(duì)應(yīng)到自定義的函數(shù)中就是thenCall方法,然后當(dāng)resolve調(diào)用的時(shí)候,就是函數(shù)內(nèi)部的用來(lái)存儲(chǔ)then的變量執(zhí)行的時(shí)候。

初始化的方法不是異步的

如果在初始化的時(shí)候,傳入的不是一個(gè)異步的函數(shù),比如下面的

const promise = new MyPromise((resolve) => {
    resolve('ssss');
});

由于執(zhí)行順序的問(wèn)題,并不會(huì)回調(diào)到then方法中??梢韵朕k法讓then方法先執(zhí)行,之后再執(zhí)行resolve,利用Js的循環(huán)機(jī)制以及setTimeout,將resolve放入棧底執(zhí)行

const thenCall = (value) => {
    setTimeout(() => {    
        thenFunc && thenFunc(value);
        thenFunc = null;
    }, 0);
};

如果對(duì)于Js的循環(huán)機(jī)制和setTimeout不是很了解的話可以參考這篇

這樣就可以確保在有then的情況下,resolve在后面執(zhí)行。

promise調(diào)用時(shí)間很晚

如果初始化Promise實(shí)例之后,并沒(méi)有立即使用它,而是間隔了一段時(shí)間再去調(diào)用promise的then方法,那么由于resolve已經(jīng)執(zhí)行完畢,所以即使是調(diào)用過(guò)了then方法也不會(huì)正確的回調(diào)。

setTimeout(() => {
    promise.then((data) => {
        console.log(data);
    }).catch((err) => {
        console.log('error' + err);
    });  
}, 1000);

之前就已經(jīng)介紹過(guò)了,Promise是有三種狀態(tài)的:pending、fulfilled、rejected。而且只能從pending轉(zhuǎn)換為另外兩種狀態(tài),而且是不可逆轉(zhuǎn)的??梢越栌肞romise的這三種狀態(tài),讓自己的Promise記住自己當(dāng)前的狀態(tài)。

添加一個(gè)記錄Promise狀態(tài)的變量,添加一個(gè)存儲(chǔ)參數(shù)的變量

function MyPromise(callBack: Function) {
  let thenFunc: ?Function = null;
  let status: string = 'pending';
  let callValue: any = null;

  this.then = (call) => {
    if (status === 'pending') {
      thenFunc = call;
    }
    call(callValue);
  };

  const thenCall = (value) => {
    setTimeout(() => {    
      thenFunc && thenFunc(value);
      callValue = value;
      status = 'fulfilled';
      thenFunc = null;
    }, 0);
  };

  callBack(thenCall);
}

在Promise初始化的時(shí)候,Promise的初始狀態(tài)為pending,執(zhí)行過(guò)thenCall方法后,改變自身狀態(tài)為fulfilled,并且將傳遞的參數(shù)暫時(shí)保存到callValue中。過(guò)一段時(shí)間后,then執(zhí)行的時(shí)候,由于狀態(tài)已經(jīng)改變,則會(huì)直接執(zhí)行then中傳遞的回調(diào)函數(shù),并將callValue作為回調(diào)參數(shù)。

添加reject狀態(tài)

上述的方法都是只有resolve一個(gè)狀態(tài),其實(shí)reject與resolve相同,僅僅是執(zhí)行了不同的操作。

function MyPromise(callBack: Function) {
  let thenFunc: ?Function = null;
  let catchFunc: ?Function = null;
  let status: string = 'pending';
  let callValue: any = null;

  this.then = (call) => {
    if (status === 'pending') {
      thenFunc = call;
    } else if (status === 'fulfilled') {
      call(callValue);
    }
    return this;
  };

  this.catch = (call) => {
    if (status === 'pending') {
      catchFunc = call;
    } else if (status === 'rejected') {
      call(callValue);
    }
    return this;
  };

  const thenCall = (value) => {
    setTimeout(() => {    
      thenFunc && thenFunc(value);
      callValue = value;
      status = 'fulfilled';
      thenFunc = null;
    }, 0);
  };

  const catchCall = (value) => {
    setTimeout(() => {    
      catchFunc && catchFunc(value);
      callValue = value;
      status = 'rejected';
      catchFunc = null;
    }, 0);
  };

  callBack(thenCall, catchCall);
}


const promise = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        reject('ssss');
    }, 1000);
});

promise.then((data) => {
    console.log(data);
}).catch((err) => {
    console.log('error' + err);
});  

關(guān)于return this,其實(shí)是將對(duì)象本身返回以供后續(xù)的catch或者其他操作,如果沒(méi)有那么會(huì)報(bào)錯(cuò)說(shuō)沒(méi)有then方法或者沒(méi)有catch方法。

鏈?zhǔn)秸{(diào)用

自身調(diào)用

Promise可以通過(guò)不停地返回Promise對(duì)象從而實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用

promise.then().then().then().....

由于MyPromise內(nèi)部是由一個(gè)變量來(lái)保存的回調(diào)函數(shù),只能保存最新的then方法,要是想實(shí)現(xiàn)自身的鏈?zhǔn)秸{(diào)用,那么就需要將其修改為數(shù)組,把每一個(gè)then傳入的回調(diào)函數(shù)保存起來(lái)。

function MyPromise(callBack: Function) {
  let thenFunc: Array<Function> = [];
  let catchFunc: ?Function = null;
  let status: string = 'pending';
  let callValue: any = null;

  this.then = (call) => {
    if (status === 'pending') {
      thenFunc.push(call);
    } else if (status === 'fulfilled') {
      call(callValue);
    }
    return this;
  };

  const thenCall = (value) => {
    setTimeout(() => {    
      callValue = value;
      status = 'fulfilled';
      thenFunc.forEach((fun) => {
        fun && fun(value);
      });
    }, 0);
  };

    ....
    ....
    
  callBack(thenCall, catchCall);
}

串行promise

更加常見(jiàn)的需求是不同的Promise類型需要在同一個(gè)方法中鏈?zhǔn)秸{(diào)用

const promise = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve('ssss');
    }, 100);
});

promise.then((data) => {
    console.log('11' + data);
    return new MyPromise((resolve) => {
        setTimeout(() => {
            resolve('dddd');
        }, 100);  
    });
}).then((data) => {
    console.log('22' + data);
}).catch((err) => {
    console.log('error' + err);
});  

// 控制臺(tái)輸入的結(jié)果為
// 11ssss
// 22ssss
// 并不是預(yù)期的
// 11ssss
// 22dddd

那么如何讓后續(xù)的then方法,返回的是最新初始化的promise呢?首先考慮的位置就是MyPromise中的then方法。因?yàn)榉祷亓艘粋€(gè)新的promise,而不是像之前的返回this,所以就沒(méi)有辦法處理新的promise里面的內(nèi)容。那么就應(yīng)該區(qū)分出來(lái),then方法里是否返回了一個(gè)新的promise。

this.then = (call) => {
    const ret = typeof call === 'function' && call(callValue);
    if (ret && ret.then && typeof ret.then === 'function') {
        // 返回了一個(gè)新的promise
    } else {
        // 正常處理
    }
};

在then方法中,首先讓傳入的回調(diào)方法執(zhí)行,觀察它的返回值是否是一個(gè)對(duì)象并且里面包含著then方法。如果有的話,該回調(diào)函數(shù)就返回了一個(gè)新的pormise。

上述的方法確實(shí)可以判斷出來(lái)是否傳入了新的promise,但是要明確的一點(diǎn)是,then方法中并不是回調(diào)函數(shù)執(zhí)行的時(shí)候,回調(diào)函數(shù)是否執(zhí)行是由thenCall控制。以上的邏輯判斷,需要傳入thenFunc數(shù)組中存儲(chǔ)起來(lái),在thenCall中執(zhí)行。

const handlePromise = (call: Function, value) => {
    const ret = typeof call === 'function' && call(value);
    if (ret && ret.then && typeof ret.then === 'function') {
        ret.then((data) => {
            call(data);
        })
    }
};

this.then = (call) => {
    if (status === 'pending') {
        thenFunc.push((value) => { handlePromise(call, value); });
    } else if (status === 'fulfilled') {
        call(callValue);
    }
    return this;
};
`將handlePromise傳入thenFunc數(shù)組中,當(dāng)其執(zhí)行的時(shí)候,會(huì)判斷then傳入回調(diào)函數(shù)的返回值。如果是一個(gè)promise對(duì)象,那么就執(zhí)行該promise的then函數(shù)`
// 控制臺(tái)輸出結(jié)果為
// 11ssss
// 22ssss
// 11dddd

控制臺(tái)輸出并不是預(yù)期的結(jié)果,但是好消息是出現(xiàn)第二個(gè)promise的數(shù)據(jù)。那么問(wèn)題在哪里呢,為什么會(huì)輸出三個(gè)結(jié)果?

// 當(dāng)?shù)谝粋€(gè)then執(zhí)行的時(shí)候,執(zhí)行到
const ret = typeof call === 'function' && call(value);
// 因?yàn)檎{(diào)用了call(value);
// 在控制臺(tái)打印了11ssss
// 之后判斷出返回了一個(gè)promise對(duì)象,執(zhí)行到
ret.then((data) => {
    call(data);
})
// 這里會(huì)執(zhí)行第二個(gè)promise的異步函數(shù)
setTimeout(() => {
    resolve('dddd');
}, 100);  
// 即在100ms后,回調(diào)dddd,注意這里面仍然是在第一個(gè)then中回調(diào)!
// 執(zhí)行到這里的時(shí)候控制臺(tái)并沒(méi)有輸出11dddd,而是先輸出了22ssss
// 因?yàn)槭且粋€(gè)100ms延遲的異步函數(shù),在調(diào)用的時(shí)候會(huì)直接執(zhí)行第二個(gè)then
// 此時(shí)MyPromise中的value值仍然是ssss,所以會(huì)直接輸出22ssss
// 100ms后輸出11dddd

到了第二次then方法的時(shí)候,仍然是第一個(gè)值,原因就是return this。仍然返回了本身的promise,而不是第二個(gè)promise中的內(nèi)容。那么可以考慮在then中每次都返回一個(gè)新的promise,用來(lái)接收新的值。

const handlePromise = (call: Function, resolve: Function) => {
    const ret = typeof call === 'function' && call(callValue);
    if (ret && ret.then && typeof ret.then === 'function') {
        ret.then((value) => {
            resolve(value);
        });
    } else {
        resolve(ret);
    }
};

this.then = (call) => {
    return new MyPromise((resolve) => {
        if (status === 'pending') {
            thenFunc.push(() => { handlePromise(call, resolve); });
        } else if (status === 'fulfilled') 
            call(callValue);
    }    
  });
};

由于在then中構(gòu)建了一個(gè)新的promise,在每次then中都會(huì)調(diào)用resolve來(lái)改變MyPromise中value的值,確保在下一次的then中獲取到新的值。

以上即可在promise中實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用。

相關(guān)API

promise.race()

Promise的race方法其實(shí)是將幾個(gè)promise一起執(zhí)行,首先回調(diào)的promise會(huì)做為race方法的回調(diào)值。

MyPromise.race = (raceList: Array<MyPromise>) => {
  return new MyPromise((resolve) => {
    let count = 0;
    for (let i = 0; i < raceList.length; i++) {
      raceList[i].then((value) => {
        if (count === 0) {
          count++;
          resolve(value);
        }
      });
    }  
  });
};

promise.all()

Promise的all方法是將幾個(gè)promise一起執(zhí)行,當(dāng)每個(gè)promise返回成功的時(shí)候,才會(huì)將所有的結(jié)果組合成一個(gè)數(shù)組返回到結(jié)果中。

MyPromise.all = (allList: Array<MyPromise>) => {
  return new MyPromise((resolve) => {
    let dataList = [];
    for (let i = 0; i < allList.length; i++) {
      allList[i].then((value) => {
        dataList.push(value);
        if (dataList.length == allList.length) {
          resolve(dataList);
        }
      });
    }  
  });
};

小結(jié)

以上是全部關(guān)于自己實(shí)現(xiàn)promise的內(nèi)容,僅僅實(shí)現(xiàn)了promise的一部分內(nèi)容。實(shí)現(xiàn)起來(lái)還是有一點(diǎn)吃力的,尤其是關(guān)于鏈?zhǔn)秸{(diào)用的實(shí)現(xiàn),更加能理解關(guān)于函數(shù)編程的思想,總體來(lái)說(shuō)實(shí)現(xiàn)的比較亂。網(wǎng)上其他大神都是用es5寫(xiě)的,和es6寫(xiě)法還是有一點(diǎn)出入的。使用es6實(shí)現(xiàn)還是比較簡(jiǎn)潔的,如果有任何問(wèn)題請(qǐng)不吝賜教。

參考鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Promise 對(duì)象 Promise 的含義 Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函...
    neromous閱讀 8,844評(píng)論 1 56
  • 你不知道JS:異步 第三章:Promises 在第二章,我們指出了采用回調(diào)來(lái)表達(dá)異步和管理并發(fā)時(shí)的兩種主要不足:缺...
    purple_force閱讀 2,261評(píng)論 0 4
  • 前言 本文旨在簡(jiǎn)單講解一下javascript中的Promise對(duì)象的概念,特性與簡(jiǎn)單的使用方法。并在文末會(huì)附上一...
    _暮雨清秋_閱讀 2,325評(píng)論 0 3
  • //本文內(nèi)容起初摘抄于 阮一峰 作者的譯文,用于記錄和學(xué)習(xí),建議觀者移步于原文 概念: 所謂的Promise,...
    曾經(jīng)過(guò)往閱讀 1,328評(píng)論 0 7
  • 特別說(shuō)明,為便于查閱,文章轉(zhuǎn)自https://github.com/getify/You-Dont-Know-JS...
    殺破狼real閱讀 962評(píng)論 0 2

友情鏈接更多精彩內(nèi)容