手寫實現Promise

# 手寫實現Promise

## Promise異步編程

  異步編程簡介:無論在瀏覽器環境還是node環境都需要JavaScript的異步編程,如在瀏覽器環境中的定時器、事件、ajax等或是node環境中的文件讀取、事件等。伴隨著異步編程就有回調機制,異步編程免不了回調。

異步編程問題:產生回調地獄,難于維護和擴展。

       try、catch只能捕捉同步代碼中出現的異常。

       同步并發的異步操作存在一定的問題。

解決方案: ES6 Promise可以解決回調地獄,以及同步并發的異步問題。

  jQuery的Callbacks和Lodash的after都是解決回調問題的其他方法

### Promise使用

```js

//excutor function 同步執行

let promise = new Promise((resolve,reject)=>{

? ? //異步操作

? ? setTimeout(()=>{

? ? ? ? Math.random()*100 > 60 ? resolve("ok"): reject("fail");

? ? },1000);

});

//Promise內部狀態,pending(等待),onFulFilled(成功),onReject(失敗)

//注冊回調,異步執行

//宏任務(setTimeout)? ? 微任務

//微任務有優先執行權

//then 可以鏈式操作

//上一個then不拋出錯誤的話,下一個then會執行成功函數

//返回值作為下一個then注冊函數的執行參數

//如果返回值為Promise對象,則下一個then的執行取決于該對象的執行函數

promise.then((val)=>{//微任務

? ? console.log(val);

? ? return new Promise((resolve,reject)=>{

? ? ? ? reject("newPromise:fail");

? ? });

},(reason)=>{

? ? console.log(reason);

? ? return "fail then1:param";

}).then((val)=>{

? ? console.log("ok then2:",val);

},(reason)=>{

? ? console.log("fail then2:",reason);

});

```

#### then 注冊回調返回值

#### catch 異常捕獲

```js

let promise = new Promise((resolve,reject)=>{

? ? throw new Error("test error");

});

//失敗函數捕獲

promise.then(null,(reason)=>{

? ? console.log(reason);

});

//鏈式調用時如果有空then,則相當于不存在可忽視

//catch捕獲

//catch后面可以繼續鏈式調用

promise.then().catch((error)=>{

? ? console.log(error);

? ? }).then((val)=>{

? ? ? ? console.log(val,"after catch: ok");

? ? },(reason)=>{

? ? ? ? console.log(reason,"after ctach: fail");

? ? })

```

#### finally 最后處理函數

#### Promise.all 同步并發異步的結果

```js

let oPro = new Promise(()=>{});

//Promise.all參數為數組,數組元素必須為Promise對象,其會將

//多個Promise實例包裝成一個新的Promise實例。

//全部成功時數組內元素的返回值組成數組,只要有失敗時返回最先被reject失敗

//狀態的值

Promise.all([oPro,oPro,oPro]).then((val)=>{

? ? console.log(val);//val為數組

});

```

#### Promise.race 誰先成功處理誰

```js

let oPro = new Promise(()=>{});

//Promise.race([p1,p2,p3]);里面的哪個結果獲得的快,就返回那個結果,

//不管結果本身成功或失敗。誰的狀態先發生改變就返回誰的狀態

Promise.race([oPro,oPro,oPro]).then((val)=>{

? ? console.log(val);

},(reason)=>{

? ? console.log(reason);

});

```

### Promise模擬實現

點擊查看 [Promise規范][promise-standard]

```js

//考慮兼容性,用ES5實現

function MyPromise(excutor) {

? ? this.status = "pending";

? ? this.resolveValue = null;

? ? this.rejectReason = null;

? ? this.resolveCallbackList = [];

? ? this.rejectCallbackList = [];

? ? try {

? ? ? ? excutor(this.resolve.bind(this), this.reject.bind(this));

? ? } catch (e) {

? ? ? ? this.reject(e);

? ? }

}

MyPromise.prototype = {

? ? resolve(val) {

? ? ? ? if (this.status === "pending") {

? ? ? ? ? ? this.status = "FulFilled";

? ? ? ? ? ? this.resolveValue = val;

? ? ? ? ? ? this.resolveCallbackList.forEach(function (cbFn) {

? ? ? ? ? ? ? ? cbFn();

? ? ? ? ? ? });

? ? ? ? }

? ? },

? ? reject(reason) {

? ? ? ? if (this.status === "pending") {

? ? ? ? ? ? this.status = "Rejected";

? ? ? ? ? ? this.rejectReason = reason;

? ? ? ? ? ? this.rejectCallbackList.forEach(function (cbFn) {

? ? ? ? ? ? ? ? cbFn();

? ? ? ? ? ? });

? ? ? ? }

? ? },

? ? then(onFulFilled, onRejected) {

? ? ? ? var self = this;

? ? ? ? self._dealNullThen(onFulFilled)._dealNullThen(onRejected);

? ? ? ? return new MyPromise(function (resolve, reject) {

? ? ? ? ? ? if (self.status === "FulFilled") {

? ? ? ? ? ? ? ? //模擬異步執行,此為宏任務,底層代碼為微任務

? ? ? ? ? ? ? ? setTimeout(function () {

? ? ? ? ? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ? ? ? ? var nextResolveValue = onFulFilled(self.resolveValue);

? ? ? ? ? ? ? ? ? ? ? ? self._dealReturnValPromse(nextResolveValue, resolve, reject);

? ? ? ? ? ? ? ? ? ? } catch (e) {

? ? ? ? ? ? ? ? ? ? ? ? reject(e);

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }, 0);

? ? ? ? ? ? } else if (self.status === "Rejected") {

? ? ? ? ? ? ? ? setTimeout(function () {

? ? ? ? ? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ? ? ? ? var nextRejectValue = onRejected(self.rejectReason);

? ? ? ? ? ? ? ? ? ? ? ? self._dealReturnValPromse(nextRejectValue, resolve, reject, true);

? ? ? ? ? ? ? ? ? ? } catch (e) {

? ? ? ? ? ? ? ? ? ? ? ? reject(e);

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }, 0);

? ? ? ? ? ? } else if (self.status === "pending") {

? ? ? ? ? ? ? ? self.resolveCallbackList.push(function () {

? ? ? ? ? ? ? ? ? ? setTimeout(function () {

? ? ? ? ? ? ? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ? ? ? ? ? ? var nextResolveValue = onFulFilled(self.resolveValue);

? ? ? ? ? ? ? ? ? ? ? ? ? ? self._dealReturnValPromse(nextResolveValue, resolve, reject);

? ? ? ? ? ? ? ? ? ? ? ? } catch (e) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? reject(e);

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? }, 0);

? ? ? ? ? ? ? ? });

? ? ? ? ? ? ? ? self.rejectCallbackList.push(function () {

? ? ? ? ? ? ? ? ? ? setTimeout(function () {

? ? ? ? ? ? ? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ? ? ? ? ? ? var nextRejectValue = onRejected(self.rejectReason);

? ? ? ? ? ? ? ? ? ? ? ? ? ? self._dealReturnValPromse(nextRejectValue, resolve, reject, true);

? ? ? ? ? ? ? ? ? ? ? ? } catch (e) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? reject(e);

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? }, 0);

? ? ? ? ? ? ? ? });

? ? ? ? ? ? }

? ? ? ? });

? ? },

? ? _dealNullThen(fn) { //處理空then情況

? ? ? ? if (!fn) {

? ? ? ? ? ? fn = function (val) {

? ? ? ? ? ? ? ? return val;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? return this;

? ? },

? ? _dealReturnValPromse(returnVal, resolve, reject, isRejected) {

? ? ? ? if (returnVal instanceof MyPromise) {

? ? ? ? ? ? //若返回值為MyPromise對象,則后面的執行狀態由該對象來決定

? ? ? ? ? ? returnVal.then(function (val) {

? ? ? ? ? ? ? ? resolve(val);

? ? ? ? ? ? }, function (reason) {

? ? ? ? ? ? ? ? reject(reason);

? ? ? ? ? ? });

? ? ? ? } else {

? ? ? ? ? ? //如果返回值不為MyPromise對象,則執行回調函數

? ? ? ? ? ? if (!isRejected) {

? ? ? ? ? ? ? ? resolve(returnVal);

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? reject(returnVal);

? ? ? ? ? ? }

? ? ? ? }

? ? },

};

MyPromise.race = function (promiseArr) {

? ? return new MyPromise(function (resolve, reject) {

? ? ? ? promiseArr.forEach(function (ele) {

? ? ? ? ? ? ele.then(resolve, reject);

? ? ? ? });

? ? });

};

//全部成功才執行成功回調,只要有一個失敗就執行失敗回調

MyPromise.all = function (promiseArr) {

? ? return new MyPromise(function (resolve, reject) {

? ? ? ? var returnValueArr = [],

? ? ? ? ? ? count = 0;

? ? ? ? for (var i = 0, len = promiseArr.length; i < len; i++) {

? ? ? ? ? ? (function (i) {

? ? ? ? ? ? ? ? promiseArr[i].then(function (val) {

? ? ? ? ? ? ? ? ? ? returnValueArr[i] = val;

? ? ? ? ? ? ? ? ? ? if (++count == len) {

? ? ? ? ? ? ? ? ? ? ? ? resolve(returnValueArr);

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }, function (reason) {

? ? ? ? ? ? ? ? ? ? reject(reason);

? ? ? ? ? ? ? ? });

? ? ? ? ? ? }(i));

? ? ? ? }

? ? });

};

```

## ES6 Symbol

數據結構:第七種數據結構Symbol

特點:唯一,可作為對象的屬性,有靜態屬性Symbol.iterator

## ES6 Iterator

&emsp;&emsp;**迭代器目的**:標準化迭代操作。

&emsp;&emsp;**迭代模式**:提供一種方法可以順序獲得聚合對象中的各個元素,是一種最簡單也是最常見的設計模式。它可以讓用戶透過特定的接口巡訪集合中的每一個元素而不用了解底層的實現。

&emsp;&emsp;**迭代器簡介**:依照與迭代模式的思想而實現,分內部迭代器和外部迭代器。

&emsp;&emsp;&emsp;&emsp;**內部迭代器**:本身是函數,該函數內部定義好迭代規則,完全接受整個迭代過程,外部只需要一次初始調用。

&emsp;&emsp;&emsp;&emsp;Array.prototype.forEach、jQuery.each內部迭代器

&emsp;&emsp;&emsp;&emsp;**外部迭代器**:本身是函數,執行返回迭代對象,迭代下一個元素必須顯式調用,調用復雜度增加,但靈活性增強。

&emsp;&emsp;&emsp;&emsp;function outerIterator(){}外部迭代器

```js

//模擬寫自己外部迭代器

function OuterIterator(o){

? ? let curIndex=0;

? ? let next=()=>{

? ? ? ? return {

? ? ? ? ? ? value:o[curIndex],

? ? ? ? ? ? done:o.length == ++curIndex,

? ? ? ? }

? ? }

? ? return {

? ? ? ? next,

? ? }

}

let arr=[1,2,3];

let oIt=outerIterator(arr);

oIt.next();

oIt.next();

oIt.next();

```

### 部署Iterator

```js

let obj={

? ? 0:"a",

? ? 1:"b",

? ? 2:"c",

? ? length:3,

? ? //要能迭代,必須部署Iterator,符合ES6

? ? [Symbol.iterator]:function (){

? ? ? ? let curIndex=0;

? ? ? ? let next = () => {

? ? ? ? ? ? return {

? ? ? ? ? ? ? ? value: this[curIndex],

? ? ? ? ? ? ? ? done: this.length == curIndex++,

? ? ? ? ? ? }

? ? ? ? };

? ? ? ? return{

? ? ? ? ? ? next,

? ? ? ? }

? ? },

}

console.log([...obj]);

```

### Generator

Generator簡介:生成器,本身為函數,執行后返回迭代對象,函數內部要配合yield使用,Generator函數分段執行,遇到yield即暫停。

特點:

&emsp;&emsp;function和函數名之間需要帶*

&emsp;&emsp;函數體內yield表達式,產出不同的內部狀態(值)

```js

//示例 Generator產生迭代對象

function *test(){

? ? let val1= yield "a";

? ? console.log(val1);//val1的值為第二次next中傳入的值

? ? yield "b";

? ? yield "c";

? ? return "d";

}

let oG=test();

oG.next();//{value:"a",done:false}

oG.next();//{value:"b",done:false}

oG.next();//{value:"c",done:false}

oG.next();//{value:"d",done:true}

```

改造前面的代碼

```js

let obj={

? ? 0:"a",

? ? 1:"b",

? ? 2:"c",

? ? length:3,

? ? //要能迭代,必須部署Iterator,符合ES6

? ? [Symbol.iterator]:function (){

? ? ? ? let curIndex=0;

? ? ? while(curIndex != this.length){

? ? ? ? ? yield this[curIndex++];

? ? ? };

? ? },

}

console.log([...obj]);

```

Generator函數使用

```js

function *read(path){

? ? let val1 = yield readFile(path);

? ? let val2 = yield readFile(val1);

? ? let val3 = yield readFile(val2);

? ? return val3;

}

let oG = read();

let {value, done} = oG.next();

value.then((val)=>{

? ? let {value, done} = oG.next();

? ? value.then((val)=>{

? ? ? ? let {value, done} = oG.next();

? ? ? ? value.then((val)=>{

? ? ? ? ? ? console.log(val);

? ? ? ? });

? ? });

});

//遞歸優化

function Co(oIterator){

? ? return new Promise((res,rej)=>{

? ? ? ? let next = (data)=>{

? ? ? ? ? ? let {value, done} = oIterator.next(data);

? ? ? ? ? ? if(done){

? ? ? ? ? ? ? ? res(value);

? ? ? ? ? ? }else{

? ? ? ? ? ? ? ? value.then((val)=>{

? ? ? ? ? ? ? ? ? ? next(val);

? ? ? ? ? ? ? ? },rej);

? ? ? ? ? ? }

? ? ? ? };

? ? ? ? next();

? ? });

}

//使用

Co(read()).then((val)=>{

? ? console.log(val);

});

```

#### Promise化

```js

let fs = require("fs");

let path="./data.txt";

let format="utf-8";

//原始函數

function readFile(){

? ? return new Promise((res,rej)=>{

? ? ? ? fs.readFile(path,format,(err,data)=>{

? ? ? ? ? ? ? ? if(err){

? ? ? ? ? ? ? ? ? ? rej(err);

? ? ? ? ? ? ? ? }else{

? ? ? ? ? ? ? ? ? ? res(data);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? });

? ? });

}

//對函數進行promise化? (npm i bluebird)

function promisify(fn){

? ? return (...arg)=>{

? ? ? ? return new Promise((res,rej)=>{

? ? ? ? ? ? fn(...arg,(err,data)=>{

? ? ? ? ? ? ? ? if(err){

? ? ? ? ? ? ? ? ? ? rej(err);

? ? ? ? ? ? ? ? }else{

? ? ? ? ? ? ? ? ? ? res(data);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? });

? ? ? ? });

? ? };

}

let readFilePromisify=promisify(fs.readFile);

readFilePromisify(path,format).then((val)=>{

? ? console.log(val);

});

//進一步對對象內異步方法進行promise化

function promisifyAll(){

? ? for(let key in obj){

? ? ? ? let fn=obj[key];

? ? ? ? if(typeof fn === "function"){

? ? ? ? ? ? obj[key + "Async"] = promisify(fn);

? ? ? ? }

? ? }

}

promisifyAll(fs);

fs.readFileAsync(path,format).then((val)=>{

? ? console.log(val);

});

```

### async & await

async簡介:async函數,是Generator語法糖,通過babel編譯后可以看出它就是Generator+Promise+Co(遞歸)思想實現的,配合await使用。

目的:優雅的解決異步操作問題。

```js

//解決回調地獄

//try catch

//同步并發的異步結果

async function read(path){

? ? try{

? ? ? ? let val1 = await readFile(path);

? ? ? ? let val2 = await readFile(val1);

? ? ? ? let val3 = await readFile(val2);

? ? }catch(e){

? ? ? ? console.log(e);//能夠捕獲異常

? ? }

? ? return val3;

}

read(path).then((val)=>{

? ? console.log(val);

});

//解決同步并發的異步問題

//Promise.all有局限性,一個異常其他也不能出結果

Promise.all([readFile(path1),readFile(path2),readFile(path3)])

.then((val)=>{

? ? console.log(val);

},(reason)=>{

? ? console.log(reason);

});

//使用async和await可以解決

```

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372