This is my Promise

Promise專題

1.什么是Promise?

Promise是JS異步編程中的重要概念,異步抽象處理對象,是目前比較流行Javascript異步編程解決方案之一。

2.對于幾種常見異步編程方案?(暫不說generator函數和async函數)

1.回調函數;
2.事件監聽
3.發布/訂閱模式
4.Promise對象

3.方案對比

這里為了突出Promise在異步編程的優勢,我主要使用了回調函數Promise作出對比

1.回調函數

我們接觸比較多的回調函數,我舉個例子,我們們用Jquery的ajax獲取數據時 都是以回調函數方式獲取的數據

$.get(url, (data) => {
    console.log(data)
)

如果說 當我們需要發送多個異步請求 并且每個請求之間需要相互依賴 那這時 我們只能 以嵌套方式來解決 形成 "回調地獄"

$.get(url, data1 => {
    console.log(data1)
    $.get(data1.url, data2 => {
        console.log(data1)
        $.get(data2.url, data3 => {
            console.log(data2)
              .....
        })
    })
})

這樣一來,在處理越多的異步邏輯時,就需要越深的回調嵌套,導致代碼書寫順序與執行順序不一致,不利于閱讀與維護,不利于bug的修復和新需求的加入等等。

2.Promise方案

這里我們使用Promise來解決這個callback-hell問題。

const asyncRequest = url => {
    return new Promise((resolve,reject)=>{
        $.get(url, data => {
            resolve(data)
        });
    })
}
首先對Jq的請求函數進行Promise封裝


// 請求data1
asyncRequest(url)
.then(data1 => {
    return request(data1.url);   
})
.then(data2 => {
    return request(data2.url);
})
.then(data3 => {
    console.log(data3);
})
.catch(err => throw new Error(`error  of ${err} happen in asyncRequest function`));

經過Promise處理的Jq請求函數,在處理上述中回調地獄情形是如此優勢巨大,整個請求下來,邏輯非常清晰和有利于代碼的維護。


接下來為了更加深入地了解Promise,我們不如造一個屬于自己的Promise吧,不過這個Promise不會完全實現和PromiseA+規范的Promise,這里我們只需要實現我們平時使用的Promise中的靜態resolve,reject函數,原型對象的then函數,catch函數,實現鏈式調用,而race和all函數這些附加性質的函數,我們有時間再去實現,我們先簡單實現上述目標!


等等,實現一個Promise之前,我們先了解Promise的特性,不然你不知道需求,寫什么代碼啊!!

3.Promise特性

1.Promise 是一個構造函數, new Promise 返回一個 promise對象 接收一個excutor執行函數作為參數, excutor有兩個函數類型形參resolve reject

const promise = new Promise((resolve, reject) => {
       // 異步處理
       // 處理結束后、調用resolve 或 reject
});

2.Promise是一個狀態機,有三種不同的狀態。

  • pending
  • fulfilled
  • rejected

(1) promise 對象初始化狀態為 pending
(2) 當調用resolve(成功),會由pending => fulfilled
(3) 當調用reject(失敗),會由pending => rejected

注意promsie狀態 只能由 pending => fulfilled/rejected, 一旦修改就不能再變

3.promise對象方法
(1) then方法注冊 當resolve(成功)/reject(失敗)的回調函數

// onFulfilled 是用來接收promise成功的值
// onRejected 是用來接收promise失敗的原因
promise.then(onFulfilled, onRejected);

(2) resolve(成功) onFulfilled會被調用

const promise = new Promise((resolve, reject) => {
   resolve('fulfilled'); // 狀態由 pending => fulfilled
});
promise.then(result => { // onFulfilled
    console.log(result); // 'fulfilled' 
}, reason => { // onRejected 不會被調用
})

(3) reject(失敗) onRejected會被調用

const promise = new Promise((resolve, reject) => {
   reject('rejected'); // 狀態由 pending => rejected
});
promise.then(result => { // onFulfilled 不會被調用
  
}, reason => { // onRejected 
    console.log(reason); // 'rejected'
})

(4) promise.catch
在鏈式寫法中可以捕獲前面then中發送的異常

promise.catch(onRejected)
相當于
promise.then(null, onRrejected);

// 注意
// onRejected 不能捕獲當前onFulfilled中的異常
promise.then(onFulfilled, onRrejected); 

// 可以寫成:
promise.then(onFulfilled)
       .catch(onRrejected); 

(5)鏈式調用
promise.then方法每次調用都返回一個新的promise對象所以可以鏈式寫法

function taskA() {
    console.log("Task A");
}
function taskB() {
    console.log("Task B");
}
function onRejected(error) {
    console.log("Catch Error: A or B", error);
}

var promise = Promise.resolve();
promise
    .then(taskA)
    .then(taskB)
    .catch(onRejected) // 捕獲前面then方法中的異常

(6)Promise的靜態方法

  • Promise.resolve 返回一個fulfilled狀態的promise對象
Promise.resolve('hello').then(function(value){
    console.log(value);
});

Promise.resolve('hello');
// 相當于
const promise = new Promise(resolve => {
   resolve('hello');
});

(2) Promise.reject 返回一個rejected狀態的promise對象

Promise.reject(24);
new Promise((resolve, reject) => {
   reject(24);
});

(3) Promise.all 接收一個promise對象數組為參數

只有全部為resolve才會調用 通常會用來處理 多個并行異步操作

const p1 = new Promise((resolve, reject) => {
   resolve(1);
});

const p2 = new Promise((resolve, reject) => {
   setTimeout(()=>{
       resolve(2);
    },1000)
   });

const p3 = new Promise((resolve, reject) => {
   reject(3);
});

Promise.all([p1, p2, p3]).then(data => { 
   console.log(data); // [1, 2, 3] 結果順序和promise實例數組順序是一致的
}, err => {
   console.log(err);
});

(4) Promise.race 接收一個promise對象數組為參數

Promise.race 只要有一個promise對象進入 FulFilled 或者 Rejected 狀態的話,就會繼續進行后面的處理。

function timerPromisefy(delay) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve(delay);
        }, delay);
    });
}
Promise.race([
    timerPromisefy(10),
    timerPromisefy(20),
    timerPromisefy(30)
]).then(function (values) {
    console.log(values); // 10
});

Promise特征總結

1.Promise是一個構造函數,接受一個函數作為參數,而該函數兩個函數類型形參resolve reject,這兩函數是構造函數內部提供。
2.Promise有三種狀態pending,fulfilled,rejected且狀態不能逆轉
3.Promise對象有三個方法then,resolve,reject,catch
4.Promise能夠進行鏈式調用
5.Promise靜態方法resolve,reject,all,rece。


需求準備好,開始早一個Promise!(all,race暫不實現,另外不對thenable對象,和內部可能存在的其他Promise的情形作處理,先通過數字字符串對象等情況)

go on

首先我們要將下面這個回調函數做做處理變成Promise形式

function doSomething(cb) {
  cb();
}

biubiubiu

function doSomething(){
    return {
        then(cb) {
            cb()
        }
    }
}

好得到這段毫無意義的代碼,不過看上去好像有點樣式...!我們目前還沒有觸及 Promise 背后的核心概念,但這也是個小小的開始。

好繼續改造

上面代碼:
function MyPromise(){
    return {
        then(cb) {
            cb()
        }
    }
}

改造代碼:
function MyPromise(excutor){
    let callback = null;  
    this.then = function(cb) {
        callback = cb
    }
    function resolve(val) {      
        callback(val)
    }
    try{
        excutor(resolve)
    }catch(error){
       throw new Error(error)
    }

}

這里改造后的代碼,好像有模有樣了,但是這里就遇到一個問題:如果你逐行執行代碼,就會發現resolve() 函數then() 函數之前被調用,這就意味著resolve() 被調用的時候,callback 還是 null。讓我們用一個 hack 來干掉這個問題,引入setTimeout處理一下,代碼如下所示:

function MyPromise(excutor){
    let callback = null;  
    this.then = function(cb) {
        callback = cb
    }
    function resolve(val) {      
        setTimeout(()=>{
           callback(val)
        },0)
    }
    try{
        excutor(resolve)
    }catch(error){
       throw new Error(error)
    }
}

測試代碼:
console.log(0)
var a = new MyPromise((resolve,reject)=>{
    console.log(1);
    resolve(2);
})
a.then(val=>{
    console.log(val)
})
console.log(3)
結果:0,1,3,2

做到這里,程序的執行順序和我們平常的Promise順序一致唉!不過還存在非常多的問題!

  • 沒有鏈式調用
  • 沒有Promise狀態
  • 沒有reject函數作錯誤返回,catch函數做錯誤捕捉等等...

好繼續改造

// promise 三個狀態
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

//這里用用es6的語法
class MyPromise {
  constructor(excutor) {
      if(typeof excutor !== 'function') {
          throw new Error(`${excutor} is not a function`);
      }
      let that = this;
      this.state = PENDING//fulfilled reject
      this.value = undefined;
      this.reason = undefined;
      this.onFulfilledCallbacks = [];//紀錄成功的回調,就是then的第一個參數
      this.onRejectedCallbacks = [];//紀錄成功的回調,就是then的第二個參數

    function resolve(value) {
       setTimeout(()=>{
           that.state = FULFILLED;
           that.value =value;
           that.onFulfilledCallbacks.forEach((cb,idx)=>{
                 cb.call(this,that.value);
           })
        })
    }
    function reject(reason) {
         setTimeout(()=>{
            that.state  = REJECTED;
            that.reason = reason;
            that.onRejectedCallbacks.forEach((cb,idx)=>{
                 cb.call(this,that.reason);
              })
           },0)
     }

      try {
         excutor(resolve,reject);
      } catch (error) {
          reject(error);
      }
  }

  then(onFulfilled,onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason=>{
            throw new Error(reason)
        }
       return new MyPromise((resolve,reject)=>{
/*
*這里是箭頭函數,所以這里this.onFulfilledCallbacks和*this.onRejectedCallbacks是指向外層的成功和失敗回調數據,
*假如是function的形式,就需要提前定義這個**參數函數**然后通過*bind()函數綁定this的指向
*/            
        this.onFulfilledCallbacks.push(value)=>{
                try {
                    let newValue = onFulfilled(value);
                    resolve(newValue);
                 } catch (error) {
                    reject(error)
                }               
             }
              this.onRejectedCallbacks.push(value=>{
                  try {
                      let reason = onRejected(value);
                      reject(reason)
                  } catch (error) {
                      reject(error)
                  }
            })
       })
  }

}

測試代碼:
console.log(0)
 var a = new SuperPromise((resolve,reject)=>{
     console.log(1)
     resolve(2)
 })

 a.then((val)=>{
     console.log(val)
     return (3)
 }).then(val=>{
     console.log(val)
 })
 console.log(4)
結果//0,1,4,2,3

好,這里我們實現了then的鏈式調用,這一看,我們只實現了原型上一個then函數,接下來我們繼續實現catch,和靜態resolve,靜態reject

go on

上面我們提到catch函數:
promise.catch(onRejected)
相當于
promise.then(null, onRrejected);
但是有一點不同的是,onRrejected不是不能捕捉到onFulfilled中的異常。

class MyPromise {
  constructor(excutor) {
      if(typeof excutor !== 'function') {
          throw new Error(`${excutor} is not a function`);
      }
      let that = this;
      this.state = PENDING//fulfilled reject
      this.value = undefined;
      this.reason = undefined;
      this.onFulfilledCallbacks = [];//紀錄成功的回調,就是then的第一個參數
      this.onRejectedCallbacks = [];//紀錄成功的回調,就是then的第二個參數

    function resolve(value) {
       setTimeout(()=>{
           that.state = FULFILLED;
           that.value =value;
           that.onFulfilledCallbacks.forEach((cb,idx)=>{
                 cb.call(this,that.value);
           })
        })
    }
    function reject(reason) {
         setTimeout(()=>{
            that.state  = REJECTED;
            that.reason = reason;
            that.onRejectedCallbacks.forEach((cb,idx)=>{
                 cb.call(this,that.reason);
              })
           },0)
     }

      try {
         excutor(resolve,reject);
      } catch (error) {
          reject(error);
      }
  }

     then(onFulfilled,onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason=>{
            throw new Error(reason)
        }        
       if(this.state === PENDING) {
            return new MyPromise((resolve,reject)=>{
                this.onFulfilledCallbacks.push(value=>{
                    try {
                        let newValue = onFulfilled(value);
                        resolve(newValue);
                    } catch (error) {
                        reject(error)
                    }
                })

                this.onRejectedCallbacks.push(value=>{
                    try {
                        let newValue = onRejected(value);
                        reject(newValue)
                    } catch (error) {
                        reject(error)
                    }
                })
            })
        }
     }

  catch(onRejected) {
      return this.then(null,onRejected)
  }
}

測試代碼:
 var b = new MyPromise((resolve,reject)=>{
     reject('error happen in SuperPromise')
 })

 b.then((val)=>{
     console.log(val,'val')
     return val;
 })
 .catch(err=>{
     console.log(err)
 })


-------
 var c = new MyPromise((resolve,reject)=>{
     resolve(2)
 })

 c.then((val)=>{
     console.log(val,'val')
     throw new Error('some error in then')
 })
 .catch(err=>{
     console.log(err)
 })

接下黎啊繼續實現靜態resolve和reject

class MyPromise {
  constructor(excutor) {
      if(typeof excutor !== 'function') {
          throw new Error(`${excutor} is not a function`);
      }
      let that = this;
      this.state = PENDING//fulfilled reject
      this.value = undefined;
      this.reason = undefined;
      this.onFulfilledCallbacks = [];//紀錄成功的回調,就是then的第一個參數
      this.onRejectedCallbacks = [];//紀錄成功的回調,就是then的第二個參數

    function resolve(value) {
       setTimeout(()=>{
           that.state = FULFILLED;
           that.value =value;
           that.onFulfilledCallbacks.forEach((cb,idx)=>{
                 cb.call(this,that.value);
           })
        })
    }
    function reject(reason) {
         setTimeout(()=>{
            that.state  = REJECTED;
            that.reason = reason;
            that.onRejectedCallbacks.forEach((cb,idx)=>{
                 cb.call(this,that.reason);
              })
           },0)
     }

      try {
         excutor(resolve,reject);
      } catch (error) {
          reject(error);
      }
  }

     then(onFulfilled,onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason=>{
            throw new Error(reason)
        }        
       if(this.state === PENDING) {
            return new MyPromise((resolve,reject)=>{
                this.onFulfilledCallbacks.push(value=>{
                    try {
                        let newValue = onFulfilled(value);
                        resolve(newValue);
                    } catch (error) {
                        reject(error)
                    }
                })

                this.onRejectedCallbacks.push(value=>{
                    try {
                        let newValue = onRejected(value);
                        reject(newValue)
                    } catch (error) {
                        reject(error)
                    }
                })
            })
        }
     }

  catch(onRejected) {
      return this.then(null,onRejected)
  }

//這兩個就比較簡單,我們回憶一下,當我們Promise.resolve(),就會馬上進入成功回調,相反Promise.reject(),就馬上進入失敗回調

  static resolve(val){
      return new MyPromise((resolve,reject)=>{
            resolve(val)
      })
  }

  static reject(reason){
      return new MyPromise((resolve,reject)=>{
          reject(reason)
      })
   }
}

測試代碼:
 MyPromise.resolve(2).then(val=>{
     console.log(val)
     return 3
 }).then(val=>{
     console.log(val)
 })

---
 MyPromise.reject('some error happen in static reject')
 .catch(err => console.log(err))

以上兩段代碼測試通過,完美!!!
好,我們再看看我們的需求

(all,race暫不實現,另外不對thenable對象,和內部可能存在的其他Promise的情形作處理,先通過數字,字符串,對象等情況)

OK這里已經實現的Promise已經實現了我們上述的請求,如果看客跟著我一起去實現一個Promise,是不是會有一些成就感呢?說笑,這節說到這里。good night

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

推薦閱讀更多精彩內容