這是你認識的Promise嗎?

這是你認識的Promise嗎?

本文字數、代碼都比較多,建議先不看代碼,有興趣的時候再仔細研究。javascript

被老學究迫害了十幾年的咱們本能的排斥嚴肅又充滿教育意義的文章。因此老是不想把技術文章寫的那麼嚴肅。今天又是一個被無數人寫過的課題。(此處略過十萬字……)依然略過做者想說的一切廢話,直入主題。java

慢着✋, 讓咱們先來想想,你但願能在這個文章中收穫什麼呢?編程

1.你知道promise是什麼嗎?

根據 百度翻譯 介紹,promise主要有下面幾種含義:數組

咳咳……不知不覺就開錯了車,籲~及時的剎住。promise

JavaScript 中的 promise 是用於處理 異步 問題的 工具。沒錯,它就是一個 工具。跟你作飯用的炒勺和鍋鏟是同一個定義。緩存

它能夠將繁瑣的異步問題經過更加易讀方式來處理,從而提升代碼的可閱讀性,減小維護所帶來的成本。異步

小栗子走一波:async

傳統調用:
function change1(data) {
  // 處理data……
  change2(data)
  function change2(data) {
    // 處理data……
  	change3(data)
  	function change3(data) {
      // 處理data……
  		change4(data)
  		function change4(data) {
        // 處理data……
  			change5(data)
  			function change5(data) {
          // 處理data……
  				change6(data)
  				function change6(data) {
  					………………
					}
				}
			}
		}
	}
}
複製代碼
Promise調用:
Promise.resolve(data)
.then(data => data) // 處理data
.then(data => data) // 處理data
.then(data => data) // 處理data
.then(data => data) // 處理data
.then(data => data) // 處理data
.then(data => data) // 處理data
.then(data => data) // 處理data
………………  
複製代碼

先拋開其餘全部的都不說,這一排整整齊齊的代碼隊伍,看上去就賞心悅目。異步編程

接下來,咱們能夠更進一步了。函數

2.同步和異步

剛纔咱們提到了,promise 是用來處理 異步 問題的工具。什麼是 異步? 不是 異步 的問題又是什麼?小朋友,你是否有不少問號 ?????

任務分爲 異步同步,不說官方的定義了,經過一段講解來講吧。

2.1 什麼是同步

**同步:**列舉一個場景,假設你如今在銀行辦理業務,若是你前面的那我的辦理不完,確定是不能給你辦理的。同一時間,業務員只能處理一個業務,當上一個業務到達完成狀態(也就是處理完)的時候,才能接受下一個業務。

2.2 什麼是異步

**異步:**一樣,列舉一個場景,場景變換到飯店,接待你的是一位服務員,服務員點完菜以後就會將菜單交到廚師手裏,而後就能夠繼續接待下一位顧客。並不須要等到當前顧客的服務結束。不須要等待複雜的、消耗時間的炒菜操做的結束。

多麼簡單的道理,茅塞頓開的感受有木有……

裝13的話就很少說了,正經起來。

3.爲何會出現promise

要解釋這個問題,得先弄懂什麼是回調地獄的問題,回調地獄出現的緣由是由於異步和回調函數。以吃披薩這件事爲慄來講,想吃披薩必須得經歷如下幾步:

想吃披薩 --> 得有披薩店 --> 店裏得有廚師 --> 廚師得有食材 --> 菜市場得出售食材 --> 收穫製做食材的原材料

其中任何一步有問題,我都不可能吃到披薩。(這裏拒絕擡槓。怕了怕了)

以代碼的形式出現:

function yuancailiao() {
  // 收穫原材料
  function chushou() {
    // 出售食材
    function shicai() {
      // 食材到披薩店裏
      function chushi() {
        // 食材到廚師手中
        function dameile() {
          // 披薩店製做披薩
          function chi () {
            // 歷經千辛萬苦,我終於吃到了披薩。
          }
        }
      }
    }
  }
}
複製代碼

當嵌套的層級太深,就會在查找代碼的時候出現暈眩的感受,讓你出現暈眩的代碼就成爲回調地獄

Promise.resolve('披薩店')
.then(data => data) // 購買原材料 
.then(data => data) // 製做披薩
.then(data => data) // 上桌
.then(data => data) // 開始大快朵eat
………………  
複製代碼

哇哦~空氣都變得清新了。(彷彿一樣的代碼我寫了兩次~~~但願能不被打)

這也就是promise出現的緣由。

4.promise的三種狀態

promise 在運行的過程當中會出現三種狀態。

狀態 描述
pending 初始狀態 表示待定
fulfilled 操做成功
rejected 操做失敗

狀態只能從 pendingfulfilled 或者 rejected 。不可逆。

圖解:

5.實現一個簡單的promise

終於到了手擼代碼的環節。準備好了嗎?come on baby……

寫代碼以前咱們先看下promise都有哪些須要實現的功能

  • 可以使用new實例化

  • 接收一個函數做爲參數

  • 函數中有 resolvereject 方法

  • 三種狀態

  • 可以使用 .then.catch 操做

  • 可拋出錯誤或執行錯誤均可經過 .catch 進行捕獲

  • 鏈式調用

好像沒有其餘的了,先寫。

第一步:定義構造函數,修改狀態
// 先定義一個Promise構造函數
function Promise (executor) {
  // 定義一個狀態,初始值爲pending
  this.status = 'pending'
  
  // 成功方法
  this.success = () => {}

  // 失敗方法
  this.error = () => {}
  
  // 定義resolve函數
  function resolve(data) {
    if (this.status === 'pending') {
      this.status = 'fulfilled'
      // 成功後執行成功方法,並將獲取的數據過去
      this.success(data)
    }
  }
  // 定義reject函數
  function reject (errorMsg) {
    if (this.status === 'pending') {
      this.status = 'rejected'
      this.error(errorMsg)
    }
  }
  
  // 將 resolve 和 reject 傳入到入參函數中,並 ---> 綁定this !!!
  executor(resolve.bind(this), reject.bind(this))
}
複製代碼
第二步:定義then、catch方法
// 定義 then 方法,接收兩個函數做爲參數 success = () => {}, error = () => {}
Promise.prototype.then = function (success, error) {
  this.success = success
  this.error = error
}

// 定義 catch 方法
Promise.prototype.catch = function (error) {
  this.error = error
}
複製代碼

好,一個嶄新的Promise就完成了。自信一試。

new Promise1((res, rej) => {
  res(1)
}).then((data) => {
  console.log(data)
})
// 什麼也沒輸出。
複製代碼

納尼。!跟個人預想徹底不相符啊。機智如我怎麼可能被一個小bug絆住了雙腳。一陣緊鑼密鼓的調試,終於讓我發現了問題的所在。在resolve方法執行的時候,this.success還未被賦值。改爲下面這樣就能夠正常輸出了。

new Promise1((res, rej) => {
  setTimeout(() => {
    res(1)
  }, 0)
}).then((data) => {
  console.log(data)
})
複製代碼
介於上述問題,咱們寫出了另外一版。請看代碼:
// 先定義一個Promise1構造函數
  function Promise1 (executor) {
    // 定義一個狀態,初始值爲pending
    this.status = 'pending'

    // 成功方法
    this.successList = []
    //
    // 失敗方法
    this.errorList = []

    // 添加 value緩存 --- 新添加 ---
    this.value = null

    // 定義resolve函數
    function resolve(data) {
      if (this.status === 'pending') {
        this.status = 'fulfilled'
        // 成功後執行成功方法,並將獲取的數據過去
        // --- 新添加 ---
        this.value = data
        this.successList.forEach(cb => cb(data))
      }
    }
    // 定義reject函數
    function reject (errorMsg) {
      if (this.status === 'pending') {
        this.status = 'rejected'
        // --- 新添加 ---
        this.value = errorMsg
        this.errorList.forEach(cb => cb(errorMsg))
      }
    }

    // 將 resolve 和 reject 傳入到入參函數中,並 ---> 綁定this !!!
    executor(resolve.bind(this), reject.bind(this))
  }
  // 定義 then 方法,接收兩個函數做爲參數 success = () => {}, error = () => {}
  Promise1.prototype.then = function (success, error) {
    // 若是執行時狀態已經更改,直接拿取緩存的值
    if (this.status === 'fulfilled') {
      success(this.value)
    }
    if (this.status === 'rejected') {
      error(this.value)
    }
    // 不然將當前函數保存
    if (this.status === 'pending') {
      this.successList.push(success)
      this.errorList.push(error)
    }
  }

  // 定義 catch 方法
  Promise1.prototype.catch = function (error) {
    if (this.status === 'pending') {
      this.error = error
      return
    }
    error(this.value)
  }

  new Promise1((res, rej) => {
    setTimeout(() => {
      res(1)
    }, 0)
  }).then((data) => {
    console.log(data)
  })
複製代碼

原諒個人罪過,讓你們一次看這麼多代碼,實屬是個人問題。之後儘可能改正。

第三步:錯誤捕獲

promise接受的參數能夠顯示的拋出錯誤,因此咱們須要將錯誤捕獲。很簡,捕獲到executor的錯誤就能夠

try {
  // 將 resolve 和 reject 傳入到入參函數中,並 ---> 綁定this !!!
  executor(resolve.bind(this), reject.bind(this))
} catch (e) {
  reject(e)
}
複製代碼
第四步:鏈式調用

鏈式調用實現的核心是須要在 then 方法中返回一個新的promise。這裏有一個須要注意的點。resolve的數據要使用resolve函數包裹,reject的數據要使用reject函數包裹。

Promise1.prototype.then = function (success, error) {
  // 若是執行時狀態已經更改,直接拿取緩存的值
  if (this.status === 'fulfilled') {
    return new Promise1((resolve, reject) => {
      try{
        resolve(success(this.value))
      }catch(e) {
        reject(e)
      }
    })
  }
  if (this.status === 'rejected') {
    return new Promise1((resolve, reject) => {
      try{
        resolve(error(this.value))
      }catch(e) {
        reject(e)
      }
    })
  }
  // 不然將當前函數保存
  if (this.status === 'pending') {
    return new Promise1((resolve, reject) => {
      this.successList.push(() => {
        try {
          resolve(success(this.value))
        } catch (e) {
          reject(e)
        }
      })
      this.errorList.push(() => {
        try {
          resolve(error(this.value))
        } catch (e) {
          reject(e)
        }
      })
    })
  }
}
複製代碼

測試一下:

new Promise1((res, rej) => {
  setTimeout(() => {
    res(1)
  }, 0)
}).then((data) => {
  console.log(data)
}).then(() => {
  console.log(2)
}).then(() => {
  console.log(3)
})
複製代碼

ok,大功告成。

6.實現promise.all 和 promise.race 方法

6.1 promise.all方法的實現

promise.all方法的特色

  • 接收一個數組,數組中的元素爲 promise 對象
  • 所有執行完成後,返回獲得的結果
  • 只要有一個報錯,直接返回錯誤信息。

栗子實現:

Promise.newAll = arr => {
  let result = [];
  return new Promise(function (resolve, reject) {
    let count = 0;
    arr.forEach(item => {
      item.then((res) => { // arr中爲promise的列表,因此直接執行then方法。
        result.push(res)
        count ++
				// 若是所有成功,經過resolve返回獲得的結果
        if (count === arr.length) {
          resolve(result)
        }
      }).catch((e) => {
        // 只要有一個報錯,就執行reject
        reject(e)
      })
    })
  })
};
複製代碼

6.2 promise.race 方法

promise.race方法的特色

  • 接收一個數組,數組中的元素爲 promise 對象
  • 返回最快獲得的內容
  • 全部內容都失敗後執行reject

栗子實現:

Promise.newRace = arr => {
  return new Promise(function (resolve, reject) {
    let count = 0;
    arr.forEach(item => {
      item.then((res) => { // arr中爲promise的列表,因此直接執行then方法。
        // 返回最快獲得的內容
        resolve(res)
      }).catch((e) => {
        count ++
				// 若是所有失敗,經過resolve返回獲得的結果
        if (count === arr.length) {
          reject(e)
        }
      })
    })
  })
};
複製代碼

7.async await 簡單介紹

不知不覺又寫成了老學究,仍是太年輕。

如今介紹一下上面這兩個傢伙吧。

async 和 awaitJavaScript提供的異步編程的語法糖(能甜死你的那種)。

先看小栗子

function see(){
  return new Promise((resolve)=>{
    setTimeout(()=>{
      resolve(1)
    },3000)
  })
}
async function test(){
  let result = await see()
  console.log(result)
}
test()
複製代碼

async 會將一個函數標記爲異步函數,經過await等待結果的返回,而且他們還會改變事件循環 (文章敬請期待) 的執行順序。而且。它們還可使用 try {} catch() {} 捕獲錯誤。

又是嘔心瀝血寫的一篇文章。看完文章的你。理解了什麼呢?

相關文章
相關標籤/搜索