深度理解Promise--Promise的特點和方法詳解

什麼是promise?

Promise(承諾),在程序中的意思就是承諾我過一段時間(通常是一個異步操作)後會給你一個結果,是異步編程的一種解決方案。從語法上說,原生Promise 是一個對象,從它可以獲取異步操作的消息。

promise的特點

  • 對象的狀態不受外界影響。

promise有三種狀態 pending(進行中) fulfilled(已成功) rejected(已失敗),只有異步操作的結果,纔可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。

  • 一旦從等待狀態變成爲其他狀態就永遠不能更改狀態了。

promise只有兩種狀態改變:
pending(進行中)--> fulfilled(已成功) ;
pending(進行中)--> rejected(已失敗)。
當狀態改變結束時稱爲resolve(已固定),一旦狀態變爲 resolved 後,就不能再次改變爲Fulfilled

  • 一旦新建Promise就會立即執行,無法中途取消。
  • 如果不設置回調函數callback,Promise內部拋出的錯誤,就不會反應到外部。
  • 當處於pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。

promise實例操作

在這裏插入圖片描述

首先創造了一個Promise實例

let promise=new Promsie(function(resolve,rejec){
    if(/*異步執行成功*/){
        resolve(value);
    }else{
        reject(error);
    }
})
promise.then(function(){
    //回調執行成功之後的操作
},function(){
    //回調執行失敗之後的操作,可選
});

Promise構造函數接受一個函數作爲參數,該函數的兩個參數分別是resolve和reject。它們是兩個函數,由 JavaScript 引擎提供。當異步操作成功時(pending--fulfilled),調用resolve(value)函數把操作結果當成參數傳出,當異步操作成功時(pending--rejected)調用 reject(error)函數把錯誤返回。Promise實例生成以後,用then方法分別指定resolved狀態和rejected狀態的回調函數。

下面看一下構造函數原型的方法

  • Promise.prototype.then()

    • Promise.prototype.then()作用是爲 Promise 實例添加狀態改變時的回調函數。接受兩個回調函數作爲參數。第一個回調函數是Promise對象的狀態變爲resolved時調用,第二個回調函數是Promise對象的狀態變爲rejected時調用。其中,第二個函數是可選的,不一定要提供。
    • Promise.prototype.then()返回的是另一個Promise對象,後面還可以接着調用then方法。
  • Promise.prototype.catch()

    • Promise.prototype.catch()則是.then(null, rejection).then(undefined, rejection)的別名,用於指定發生錯誤時的回調函數。 Promise 對象的錯誤具有“冒泡”性質,會一直向後傳遞,直到被捕獲爲止。也就是說,錯誤總是會被下一個catch語句捕獲
    • Promise.catch()方法返回的也是一個 Promise 對象,因此後面還可以接着調用then方法。

上述代碼也可以理解成這樣:

getJSON('/posts.json').then(function(posts) {
      // ...
}).catch(function(error) {
  // 處理 getJSON 和 前一個回調函數運行時發生的錯誤
  console.log('發生錯誤!', error);
});
  • Promise.prototype.finally()

    • finally方法用於指定不管 Promise 對象最後狀態如何,都會執行的回調函數。該方法是 ES2018 引入標準的。
    • finally方法的回調函數不接受任何參數,這意味着沒有辦法知道,前面的 Promise 狀態到底是fulfilled還是rejected。這表明,finally方法裏面的操作,應該是與狀態無關的,不依賴於 Promise 的執行結果。
    • finally本質上是then方法的特例。

      promise.then(()=>{}).catch(()=>{}).finally(() => {
            // 操作
      });
      // 等同於
      promise.then(result => {
              // 操作
          return result;
      }).catch( error => {
              // 操作
          throw error;
      });
  • promise的鏈式調用

    • 由於 .then每次調用返回的都是一個新的Promise實例,如果then中返回的是一個結果的話會把這個結果傳遞下一次then中的成功回調,所以可以鏈式調用該實例。
    • 如果then中出現異常,會走下一個then的失敗回調,catch 會捕獲到沒有捕獲的異常。
    • 在 then中使用了return,那麼 return 的值會被Promise.resolve() 包裝,then中也可以不傳遞參數,如果不傳遞會透到下一個then中。

      Promise.resolve(1).then(res => {
              console.log(res)
              return 2 //包裝成 Promise.resolve(2)
      }).catch(err => 3).then().then(res => console.log(res))

promise自身API

  • Promise.resolve()

將現有的對象轉換(包裝)成 promise對象。
四種參數類型:

    • 不帶參數傳遞 --- 返回一個新的狀態爲resolve的promise對象。

      let p = Priomse.resolve()   // p就是promise
    • 參數是一個 Promise 實例--- 返回 當前的promise實例
    • 參數是帶then方法的對象

      let data = {
          then:function(resolve,reject){
              resovle('帶then方法的對象')
          }
      }
      Promise.resolve(data).thne((res)=> console.log(res)) // '帶then方法的對象'

      返回一個新的promise,並直接執行then的方法,promise對象的狀態就變爲resolved,從而立即執行最後那個then方法指定的回調函數,輸出 '帶then方法的對象'

    • 參數是非空,非then方法的對象,非proimse的

      let p = Promise.resolve('foo')
      // 等價於
      let p = new Promise(resolve => resolve('foo'))
      p.then(res=>console.log(res)) //'foo'

      返回一個新的狀態爲resolve的promise對象,所以then回調函數會立即執行。Promise.resolve方法的參數,會同時傳給回調函數。

    • Promise.reject()

      • 參數爲非then對象時-----Promise.reject(reason)方法也會返回一個新的 Promise 實例,該實例的狀態爲rejected

        let p  =  Promise.reject('error')
        // 等價於
        let p = new Primose((resolve,reject)=>reject('出錯了')})
        //處理錯誤的回調
        p.then((null,res)=>console.log(res)) //'出錯了'
      • 參數是帶then方法的對象 ---返回的並不是then方法的回調函數,而是data對象本身

        let data = {
            then:function(resolve,reject){
                reject('帶then方法的對象出錯了')
            }
        }
        Promise.resolve(data).thne((null,res)=> console.log(res)) // data 
        //等同於
        Promise.resolve(data).catch(res=> console.log(res)) // data 
    • Promise.all()
      該方法將多個promise實例,包裝成一個新的promise實例。

          let p = Promise.all([p1,p2,p3])

      參數不一定爲數組,但必須爲一個可迭代Iterator ,且返回的每個成員(p1,p2,p3)都是 Promise 實例,如果不是,就會先調用的Promise.resolve方法,將參數轉爲 Promise 實例,再進一步處理。

      var p = Promise.all([1,2,3]);
      var p2 = Promise.all([1,2,3, Promise.resolve(444)]);
      var p3 = Promise.all([1,2,3, Promise.reject(555)]);
      setTimeout(function() {
          console.log(p);// Promise { <state>: "fulfilled", <value>: Array[3] }
             console.log(p2); // Promise { <state>: "fulfilled", <value>: Array[4] }
          console.log(p3); // Promise { <state>: "rejected", <reason>: 555 }
      });
      p.then(function (posts) {
        // ..當有返回值的時候纔會回調
      }).catch(function(reason){
        // ...
      });
      • p1,p2,p3中得實例都改變成 fulfilled(已成功)時,此時p1、p2、p3的返回值組成一個數組,傳遞給p的回調函數。
      • p1,p2,p3中得實例其中一項的改變成 rejected(已失敗)時,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。
      • Promise.all()是異步解析,只有這當所有實例的狀態都變成fulfilled,或者其中有一個變爲rejected,纔會調用Promise.all方法後面的回調函數then,catch方法。但是當且僅當傳遞的iterable爲空時,Promise.all纔會同步解析

        var p = Promise.all([]); 
        console.log(p);//Promise { <state>: "fulfilled", <value>: Array[0] }
      • 處理錯誤,常規情況下,當其中一個實例返回rejected,就會調用Promise.allcatch方法,返回第一個錯誤。但實際應用時,我們想讓所有的實例不論成功或失敗就可以返回參數組成數組,這時就可以調用實例自身的catch方法來規避這種情況。

        const p1 = new Promise((resolve, reject) => {
          resolve('hello'); //resolved
        }).then(result => result).catch(e => e);
        
        const p2 = new Promise((resolve, reject) => {
          throw new Error('報錯了');//rejected
        }).then(result => result).catch(e => e);
        
        Promise.all([p1, p2])
        .then(result => console.log(result))// ["hello", Error: 報錯了]
        .catch(e => console.log(e));
        

    p1會resolved,p2首先會rejected,但是p2有自己的catch方法,該方法返回的是一個新的 Promise 實例,p2指向的實際上是這個實例。該實例執行完catch方法後,也會變成resolved,導致Promise.all()方法參數裏面的兩個實例都會resolved,因此會調用then方法指定的回調函數,而不會調用catch方法指定的回調函數。

    • js原生實現Promise.all的原理

      //在Promise類上添加一個all方法,接受一個傳進來的promise數組
      Promise.all = function (promiseArrs) { 
         return new Promise((resolve, reject) => { //返回一個新的Promise
          let arr = []; //定義一個空數組存放結果
          let i = 0;
          function handleData(index, data) { //處理數據函數
              arr[index] = data;
              i++;
              if (i === promiseArrs.length) { //當i等於傳遞的數組的長度時 
                  resolve(arr); //執行resolve,並將結果放入
              }
          }
          for (let i = 0; i < promiseArrs.length; i++) { //循環遍歷數組
              promiseArrs[i].then((data) => {
                  handleData(i, data); //將結果和索引傳入handleData函數
              }, reject)
          }
          })
      }
    • 如果說all體驗不好,那我們也可以自己做一個some方法,表示全部失敗纔算失敗

      Promise.some = function (promiseArrs) {
        return new Promise((resolve, reject) => {
        let arr = []; //定義一個空數組存放結果
        let i = 0;
        function handleErr(index, err) { //處理錯誤函數
            arr[index] = err;
            i++;
            if (i === promiseArrs.length) { //當i等於傳遞的數組的長度時 
              reject(err); //執行reject,並將結果放入
            }
        }
        for (let i = 0; i < promiseArrs.length; i++) { //循環遍歷數組
            promiseArrs[i].then(resolve, (e) => handleErr(i, e))
        }
        })
      }
    • Promise.allSettled -- 兼容性不友好
      該方法和promise.all類似,就是解決all方法在處理錯誤時的不合理而出現的。其參數接受一個Promise的數組, 返回一個新的Promise, 唯一與all的不同在於, 其不會進行短路, 也就是說當Promise全部處理完成後我們可以拿到每個Promise的狀態, 而不管其是否處理成功。

      • 和all類似,當自身實例有catch回調時,每個實例狀態變爲fulfilled

        const p3 = new Promise((resolve, reject) => {
          resolve('hello'); //resolved
        }).then(result => result).catch(e => e);
        
        const p4 = new Promise((resolve, reject) => {
          throw new Error('報錯了');//rejected
        }).then(result => result).catch(e => e);
        
        Promise.allSettled([p3, p4])
        .then(result => console.log(result))
        .catch(e => console.log(e));
        //.then的log
        //[{status: "fulfilled", value: "hello"},{status: "fulfilled", reason: Error: 報錯了 at <anonymous>:6:10 at     new Promise (<anonymous>) at <anonymous>:5:13}]
      • 沒有catch接收錯誤,返回自身的狀態和回調參數

        const p5 = new Promise((resolve, reject) => {
          resolve('hello'); //resolved
        }).then(result => result)
        
        const p6 = new Promise((resolve, reject) => {
          throw new Error('報錯了');//rejected
        }).then(result => result)
        
        Promise.allSettled([p5, p6])
        .then(result => console.log(result))
        .catch(e => console.log(e));
        //.then的log
        //[{status: "fulfilled", value: "hello"},{status: "rejected", reason: Error: 報錯了 at <anonymous>:6:10 at     new Promise (<anonymous>) at <anonymous>:5:13}]
    • Promise.race()

    該方法同樣是將多個 Promise 實例,包裝成一個新的 Promise 實例,其他特點和all很像,和all的區別在於:race方法好比是賽跑,幾個實例一起跑,誰先到就成功了,就resolve誰,或者誰跑到中途摔了出現異常狀況失敗了,就reject誰,不論成功還是失敗,就先捕獲第一個完成的。

    • 捕獲第一個成功的實例回調函數

      let p1 = Promise.resolve('1')
      let p2 = Promise.resolve('2')
      Promise.race([p1,p2]).then(res=>conseloe.log(res))// '1'
    • 捕獲第一個結果

      let p1 = Promise.resolve("1");
        let p2 = Promise.reject("ERR2");
       Promise.race([p1,p2]).then(res=>conseloe.log(res)) //Promise {<resolved>: "1"}
    • 捕獲第一個錯誤

      let p1 = Promise.reject("ERR1");
        let p2 = Promise.reject("ERR2");
       Promise.race([p1,p2]).catch(console.log) //Promise {<reject>: "ERR1"}
    • 原生實現Promise.race()的設計原理

      Promise._race = iterator  =>{
          return new Promise((resolve,reject)=>{
              iterator.forEach(item=>{
                  Promise.resolve(item).then(resolve).catch(reject)
              })
          })
      }
    • Promise.try-- 提案

    在實際開發使用promise時,希望經過promise包裝後的函數內部代碼讓同步函數同步執行,異步函數異步執行,並且讓它們具有統一的 API
    例:當同步函數被promise包裝後的執行順序改變。

    let fn = () =>console.log('同步1');
    Promise.resolve().then(fn)
    console.log('同步2')
    //log後
    //'同步2'
    //'同步1'
    • 解決讓同步函數同步執行,異步函數異步執行現階段方法

      • 方法一:使用async匿名函數,會立即執行裏面的async函數,因此如果f是同步的,就會得到同步的結果;如果f是異步的,就可以用then指定下一步,如果想捕獲錯誤,使用catch方法。

         let fn = () =>console.log('同步1');
         (async ()=>fn())()
         .then(resolve)
         .catch(err=>console.log(err))
         console.log('同步2')
         //log後
         //'同步1'
        //'同步2'
      • 方法二:使用promise立即執行的匿名函數

         let fn = () =>console.log('同步1');
        (
            () => new Promise(
               resolve => resolve(fn())
         ))()
        console.log('同步2')
        //log後
        //'同步1'
           //'同步2'
    • Promise.try的應用
      該方法是用來模擬try的代碼塊的,就像promise.catch模擬的是catch代碼塊。

      • 理解 try catch finally

         try catch是JavaScript的異常處理機制,把可能出錯的代碼放在try語句塊中,如果出錯了,就會被catch捕獲來處理異常。如果不catch 一旦出錯就會造成程序崩潰。finally:無論結果如何,允許在 try 和 catch 之後執行代碼。
        try {
                // 供測試的代碼塊
        }
         catch(err) {
                 //處理錯誤的代碼塊
        } 
        finally {
                 //無論 try / catch 結果如何都執行的代碼塊
        }
      • 應用

        let fn = () => console.log('同步1');
          Promise.try(fn);
          console.log('同步2');
          //'同步1'
          //'同步2'

    over~有問題留言
    拓展:

    借鑑:
    https://blog.csdn.net/sjw1039...
    http://es6.ruanyifeng.com/#do...
    https://developer.mozilla.org...

    發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章