完全搞定Promise

<!doctype html>promisehtml

promise緣由

簡單來講 就是爲了解決callback hell 簡單來看一個回調的例子vue

 
 
 
x
 
 
 
 
1
let fs = require('fs')
2
fs.readFile('./a.txt','utf8',function(err,data){
3
  fs.readFile(data,'utf8',function(err,data){
4
    fs.readFile(data,'utf8',function(err,data){
5
      console.log(data)
6
    })
7
  })
8
})
9
 
 

 

因爲括號過多過於複雜很難判斷具體 某函數回調是哪個。在引入了promise後 事情就變得簡單了不少了。node

 
 
 
xxxxxxxxxx
19
 
 
 
 
1
let fs = require('fs')
2
function read(url){
3
  return new Promise((resolve,reject)=>{
4
    fs.readFile(url,'utf8',function(error,data){
5
      error && reject(error)
6
      resolve(data)
7
    })
8
  })
9
}
10
11
read('./a.txt')
12
  .then(data=>{
13
  return read(data)})
14
  .then(data=>{
15
  return read(data)})
16
  .then(data=>{
17
  console.log(data)
18
})
19
 
 

 

手寫簡單promise

promise標準

首先咱們要知道本身手寫一個Promise,應該怎麼去寫,誰來告訴咱們怎麼寫,須要遵循什麼樣的規則。固然這些你都不用擔憂,其實業界都是經過一個規則指標來生產Promise的。讓咱們來看看是什麼東西。Promise/A+web

 

Constructor

Constructor表示構造函數,先來實現一個最簡單的promise。分析一下,最簡單的promise包含如下幾種主要功能windows

1.執行函數數組

2.reslove[做爲成功調用時候的回調函數]promise

3.reject [做爲失敗調用時候的回調函數]app

那麼簡單來實現的話 應該是下面這種框架框架

 
 
 
xxxxxxxxxx
7
 
 
 
 
1
new promise = 
2
function (執行函數 ) 
3
{
4
reslove ()
5
 
        
6
reject  ()
7
}
 
 

Okk 那麼如今按照ES6 類的方法來完善上面的僞代碼:less

 
 
 
xxxxxxxxxx
35
 
 
 
 
1
class Promise{
2
  constructor(executor){
3
    //控制狀態,使用了一次以後,接下來的都不被使用
4
    this.status = 'pendding'
5
    this.value = undefined
6
    this.reason = undefined
7
    
8
    //定義resolve函數
9
    let resolve = (data)=>{
10
      //這裏pendding,主要是爲了防止executor中調用了兩次resovle或reject方法,而咱們只調用一次
11
      if(this.status==='pendding'){
12
        this.status = 'resolve'
13
        this.value = data
14
      } 
15
    }
16
17
    //定義reject函數
18
    let reject = (data)=>{
19
      if(this.status==='pendding'){
20
        this.status = 'reject'        
21
        this.reason = data
22
      } 
23
    }
24
25
    //executor方法可能會拋出異常,須要捕獲
26
    try{
27
      //將resolve和reject函數給使用者      
28
      executor(resolve,reject)      
29
    }catch(e){
30
      //若是在函數中拋出異常則將它注入reject中
31
      reject(e)
32
    }
33
  }
34
}
35
 
 

then 方法

若是要說promise的話,大部分人的第一印象都是then回調。可見then方法對於promise的重要性。

簡單來講,then的功能就是,傳入成功和失敗兩種狀況下的回調函數,then方法出現,便於promise的更簡潔使用,其基礎功能代碼也很簡單:

 
 
 
xxxxxxxxxx
13
 
 
 
 
1
then(onFufilled,onRejected)
2
{  
3
  
4
  if(this.status === 'resolve'){
5
    onFufilled(this.value)
6
  }
7
  
8
  if(this.status === 'reject'){
9
    onRejected(this.reason)
10
  }
11
  
12
}
13
 
 

onFulfilled 特性 若是 onFulfilled 是函數:

當 promise 執行結束後其必須被調用,其第一個參數爲 promise 的終值

在 promise 執行結束前其不可被調用

其調用次數不可超過一次

onRejected 特性 若是 onRejected 是函數:

當 promise 被拒絕執行後其必須被調用,其第一個參數爲 promise 的據因

在 promise 被拒絕執行前其不可被調用

其調用次數不可超過一次

 

異步的Promise實現

剛剛其實只能應對同步的狀況,異步的時候就須要藉助callback來進行實現了,當reslovereject被執行後 去執行對應的callback

 
 
 
xxxxxxxxxx
20
 
 
 
 
1
//存放成功回調的函數
2
this.onResolvedCallbacks = []
3
//存放失敗回調的函數
4
this.onRejectedCallbacks = []
5
6
let resolve = (data)=>{
7
  if(this.status==='pendding'){
8
    this.status = 'resolve'
9
    this.value = data
10
    //監聽回調函數
11
    this.onResolvedCallbacks.forEach(fn=>fn())
12
  } 
13
}
14
let reject = (data)=>{
15
  if(this.status==='pendding'){
16
    this.status = 'reject'        
17
    this.reason = data
18
    this.onRejectedCallbacks.forEach(fn=>fn())
19
  } 
20
}
 
 

 

那麼對於then咱們也須要添加一些東西來進行適配,判斷當Promise是一步操做時候,須要在咱們以前定義的回調函數組中添加一個回調函數

 
 
 
xxxxxxxxxx
12
 
 
 
 
1
if(this.status === 'pendding'){
2
  this.onResolvedCallbacks.push(()=>{
3
    // to do....
4
    let x = onFufilled(this.value)
5
    resolvePromise(promise2,x,resolve,reject)
6
  })
7
  this.onRejectedCallbacks.push(()=>{
8
    let x = onRejected(this.reason)
9
    resolvePromise(promise2,x,resolve,reject)
10
  })
11
}
12
 
 

 

無限then的祕籍

這也是Promise中的重頭戲,咱們在用Promise的時候可能會發現,當then函數中return了一個值,咱們能夠繼續then下去,不過是什麼值,都能在下一個then中獲取,還有,當咱們不在then中放入參數,例:promise.then().then(),那麼其後面的then依舊能夠獲得以前then返回的值,來看看具體的實現吧

 
 
 
xxxxxxxxxx
65
 
 
 
 
1
then(onFufilled,onRejected){ 
2
    //解決onFufilled,onRejected沒有傳值的問題
3
    onFufilled = typeof onFufilled === 'function'?onFufilled:y=>y
4
    //由於錯誤的值要讓後面訪問到,因此這裏也要跑出個錯誤,否則會在以後then的resolve中捕獲
5
    onRejected = typeof onRejected === 'function'?onRejected:err=>{ throw err ;}
6
    //聲明一個promise對象
7
    let promise2
8
    if(this.status === 'resolve'){
9
      //由於在.then以後又是一個promise對象,因此這裏確定要返回一個promise對象
10
      promise2 = new Promise((resolve,reject)=>{
11
        setTimeout(()=>{
12
          //由於穿透值的緣故,在默認的跑出一個error後,不能再用下一個的reject來接受,只能經過try,catch        
13
          try{
14
            //由於有的時候須要判斷then中的方法是否返回一個promise對象,因此須要判斷
15
            //若是返回值爲promise對象,則須要取出結果看成promise2的resolve結果
16
            //若是不是,直接做爲promise2的resolve結果
17
            let x = onFufilled(this.value)
18
            //抽離出一個公共方法來判斷他們是否爲promise對象
19
            resolvePromise(promise2,x,resolve,reject)
20
          }catch(e){
21
            reject(e)
22
          }
23
        },0)
24
      })
25
    }
26
    if(this.status === 'reject'){
27
      promise2 = new Promise((resolve,reject)=>{
28
        setTimeout(()=>{
29
          try{
30
            let x = onRejected(this.reason)
31
            resolvePromise(promise2,x,resolve,reject)
32
          }catch(e){
33
            reject(e)
34
          }
35
        },0)
36
      })
37
    }
38
    if(this.status === 'pendding'){
39
      promise2 = new Promise((resolve,reject)=>{
40
        this.onResolvedCallbacks.push(()=>{
41
          // to do....
42
          setTimeout(()=>{
43
            try{
44
              let x = onFufilled(this.value)
45
              resolvePromise(promise2,x,resolve,reject)
46
            }catch(e){
47
              reject(e)
48
            }
49
          },0)
50
        })
51
        this.onRejectedCallbacks.push(()=>{
52
          setTimeout(()=>{
53
            try{
54
              let x = onRejected(this.reason)
55
              resolvePromise(promise2,x,resolve,reject)
56
            }catch(e){
57
              reject(e)
58
            }
59
          })
60
        })
61
      })
62
    }
63
    return promise2   //首先要確保then返回值爲promise 才能夠保證then的鏈式循環
64
  }
65
 
 

 

1.返回Promise?:首先咱們要注意的一點是,then有返回值,then了以後還能在then,那就說明以前的then返回的必然是個Promise

2.爲何外面要包一層setTimeout?:由於Promise自己是一個異步方法,屬於微任務一列,必須得在執行棧執行完了在去取他的值,因此全部的返回值都得包一層異步setTimeout。

3.爲何開頭有兩個判斷?:這就是以前想要解決的若是then函數中的參數不是函數,那麼咱們須要作處理。若是onFufilled不是函數,就須要自定義個函數用來返回以前resolve的值,若是onRejected不是函數,自定義個函數拋出異常。這裏會有個小坑,若是這裏不拋出異常,會在下一個then的onFufilled中拿到值。又由於這裏拋出了異常因此全部的onFufilled或者onRejected都須要try/catch,這也是Promise/A+的規範。固然本人以爲成功的回調不須要拋出異常也能夠,你們能夠仔細想一想。

4.resolvePromise是什麼?:這實際上是官方Promise/A+的需求。由於你的then能夠返回任何職,固然包括Promise對象,而若是是Promise對象,咱們就須要將他拆解,直到它不是一個Promise對象,取其中的值。

它的做用是用來將onFufilled的返回值進行判斷取值處理,把最後得到的值放入最外面那層的Promise的resolve函數中。

 

reslovepromise

 
 
 
xxxxxxxxxx
39
 
 
 
 
1
function resolvePromise(promise2,x,resolve,reject){
2
  //判斷x和promise2之間的關係
3
  //由於promise2是上一個promise.then後的返回結果,因此若是相同,會致使下面的.then會是同一個promise2,一直都是,沒有盡頭
4
  if(x === promise2){//至關於promise.then以後return了本身,由於then會等待return後的promise,致使本身等待本身,一直處於等待
5
    return reject(new TypeError('循環引用'))
6
  }
7
  //若是x不是null,是對象或者方法
8
  if(x !== null && (typeof x === 'object' || typeof x === 'function')){
9
    //爲了判斷resolve過的就不用再reject了,(好比有reject和resolve的時候)
10
    let called
11
    try{//防止then出現異常,Object.defineProperty
12
      let then = x.then//取x的then方法可能會取到{then:{}},並無執行
13
      if(typeof then === 'function'){
14
        //咱們就認爲他是promise,call他,由於then方法中的this來自本身的promise對象
15
        then.call(x,y=>{//第一個參數是將x這個promise方法做爲this指向,後兩個參數分別爲成功失敗回調
16
          if(called) return;
17
          called = true
18
          //由於可能promise中還有promise,因此須要遞歸
19
          resolvePromise(promise2,y,resolve,reject)
20
        },err=>{
21
          if(called) return;
22
          called = true
23
          //一次錯誤就直接返回
24
          reject(err)
25
        })
26
      }else{
27
        //若是是個普通對象就直接返回resolve做爲結果
28
        resolve(x)
29
      }
30
    }catch(e){
31
      if(called) return;
32
      called = true
33
      reject(e)
34
    }
35
  }else{
36
    //這裏返回的是非函數,非對象的值,就直接放在promise2的resolve中做爲結果
37
    resolve(x)
38
  }
39
}
 
 

 

promise全完善

catch

簡單來講,就是用來捕獲Promise中rejecet的值,也就是至關於then方法中的OnRejected回調函數

 
 
 
xxxxxxxxxx
3
 
 
 
 
1
catch(onRejected){
2
  return this.then(null,onRejected)
3
}
 
 

該方法是掛在Promise原型上的方法。當咱們調用catch傳callback的時候,就至關因而調用了then方法。

 

reslove/reject

對於Promsie.reslovePromise.reject來說,它本質是返回一個Promise對象

 
 
 
xxxxxxxxxx
13
 
 
 
 
1
//resolve方法
2
Promise.resolve = function(val){
3
  return new Promise((resolve,reject)=>{
4
    resolve(val)
5
  })
6
}
7
//reject方法
8
Promise.reject = function(val){
9
  return new Promise((resolve,reject)=>{
10
    reject(val)
11
  })
12
}
13
 
 

 

all

all方法能夠說是Promise中很經常使用的方法了,它的做用就是將一個數組的Promise對象放在其中,當所有resolve的時候就會執行then方法,當有一個reject的時候就會執行catch,而且他們的結果也是按着數組中的順序來排放的,那麼咱們來實現一下。

 
 
 
xxxxxxxxxx
20
 
 
 
 
1
//all方法(獲取全部的promise,都執行then,把結果放到數組,一塊兒返回)
2
Promise.all = function(promises){
3
  let arr = []
4
  let i = 0
5
  function processData(index,data){
6
    arr[index] = data
7
    i++
8
    if(i == promises.length){
9
      resolve(arr)
10
    }
11
  }
12
  return new Promise((resolve,reject)=>{
13
    for(let i=0;i<promises.length;i++){
14
      promises[i].then(data=>{
15
        processData(i,data)
16
      },reject)
17
    }
18
  })
19
}
20
 
 

 

其原理就是將參數中的數組取出遍歷,每當執行成功都會執行processData方法,processData方法就是用來記錄每一個Promise的值和它對應的下標,當執行的次數等於數組長度時就會執行resolve,把arr的值給then。這裏會有一個坑,若是你是經過arr數組的長度來判斷他是否應該resolve的話就會出錯,爲何呢?由於js數組的特性,致使若是先出來的是1位置上的值進arr,那麼0位置上也會多一個空的值,因此不合理

 

 
 
 
xxxxxxxxxx
20
 
 
 
 
1
Promise.all = function (p) {
2
  let arr = []
3
  let i = 0
4
  function processDate(index, data) {
5
    arr[index] = data
6
    i++
7
    if (i == p.length) {
8
      resolve(arr)
9
    }
10
  }
11
12
  return new Promise((resolve, reject) => {
13
    for (let i = 0; i < p.length; i++) {
14
      p(i).then (data => {
15
        processDate(i,data)
16
      },reject)
17
    }
18
  })
19
}
20
 
 

 

race

race方法雖然不經常使用,可是在Promise方法中也是一個能用得上的方法,它的做用是將一個Promise數組放入race中,哪一個先執行完,race就直接執行完,並從then中取值。咱們來實現一下吧。

 
 
 
xxxxxxxxxx
7
 
 
 
 
1
Promise.race = function(promises){
2
  return new Promise((resolve,reject)=>{
3
    for(let i=0;i<promises.length;i++){
4
      promises[i].then(resolve,reject)
5
    }
6
  })
7
}
相關文章
相關標籤/搜索