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

什麼是promise?

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

promise的特色

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

promise有三種狀態 pending(進行中) fulfilled(已成功) rejected(已失敗),只有異步操做的結果,才能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。java

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

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

  • 一旦新建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狀態的回調函數。es6

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

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方法。

上述代碼也能夠理解成這樣:ajax

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中。json

    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對象。四種參數類型:axios

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

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

    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(已成功)時,此時p一、p二、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'
  • 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'
解決讓同步函數同步執行,異步函數異步執行現階段方法
  • 方法一:使用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'

over~有問題留言
拓展:

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

相關文章
相關標籤/搜索