八. ES6的Promise詳解

1. Promise理解

ES6中一個很是重要和好用的特性就是Promise類。javascript

可是初次接觸Promise會一臉懵逼,這TM是什麼東西?看看官方或者一些文章對它的介紹和用法也是一頭霧水。java

Promise究竟是作什麼的呢? => Promise是異步編程的一種解決方案。通常狀況下有異步操做時,使用Promise對這個異步操做進行封裝。ajax

那何時咱們會來處理異步事件呢?一種很常見的場景應該就是 網絡請求 了。編程

咱們封裝一個網絡請求的函數不能當即拿到結果,因此不能像簡單的 3+4=7 同樣將結果返回。因此咱們每每會傳入另一個函數,在數據請求成功時將數據經過傳入的函數回調出去。若是隻是一個簡單的網絡請求,那麼這種方案不會給咱們帶來很大的麻煩。可是當網絡請求很是複雜時就會出現回調地獄。promise

OK,我以一個很是誇張的案例來講明。咱們來考慮下面的場景(有誇張的成分):服務器

  • 咱們須要經過一個url1從服務器加載一個數據data1,data1中包含了下一個請求的url2
  • 咱們須要經過data1取出url2,從服務器加載數據data2,data2中包含了下一個請求的url3
  • 咱們須要經過data2取出url3,從服務器加載數據data3,data3中包含了下一個請求的url4
  • 發送網絡請求url4,獲取最終的數據data4
$.ajax('url1',function(data1) {
   $.ajax(data1['url2'],function(data2) {
     $.ajax(data2['url3'],function(data3) {
       $.ajax(data3['url4'],function(data4) {
         console.log(data4);
       })
     })
   })
})

上面的代碼有什麼問題嗎?正常狀況下不會有什麼問題,能夠正常運行而且獲取咱們想要的結果。可是這樣額代碼難看並且不容易維護,咱們更加指望的是一種更加優雅的方式來進行這種異步操做。如何作呢?就是使用Promise,Promise能夠以一種很是優雅的方式來解決這個問題。網絡

2. Promise基本使用

2.1 定時器的異步事件案例

咱們先來看看Promise最基本的語法。異步

這裏咱們用一個定時器來模擬異步事件:假設下面的data是從網絡上1秒後請求的數據,console.log就是咱們的處理方式。異步編程

setTimeout(() => {
	console.log('Hello World');
},1000)

上面是咱們過去的處理方式,咱們將它用Promise進行封裝(雖然這個例子會讓咱們感受脫褲放屁畫蛇添足)函數

  • 首先下面的Promise代碼明顯比上面的代碼看起來還要複雜。
  • 其次下面的Promise代碼中包含的resolve、reject、then、catch都是些什麼東西?
  • 咱們先無論複雜度的問題,由於這樣的一個屁大點的程序根本看不出來Promise真正的做用。
new Promise((resolve,reject) => {
   setTimeout(() => {
     resolve('Hello World')
     //reject('Error Data')
   }, 1000)
 }).then(data => {
   console.log(data) //Hello World
 }).catch(error => {
   console.log(error) //Error Data
})
//注意:另外一種寫法,成功和失敗的消息均可以寫在then這個回調函數中
new Promise((resolve,reject) => {
   setTimeout(() => {
     resolve('Hello World')
     //reject('Error Data')
   }, 1000)
 }).then(data => {
   console.log(data) //Hello World
 },error => {
   console.log(error) //Error Data
})

咱們先來認認真真的讀一讀這個程序到底作了什麼?

  • new Promise很明顯是建立一個Promise對象

  • 小括號中(resolve, reject) => {}也很明顯就是一個函數,並且咱們這裏用的是箭頭函數

    • 可是resolve, reject它們是什麼呢?

    • 咱們先知道一個事實:在建立Promise時傳入的這個箭頭函數是固定的(通常咱們都會這樣寫)

    • resolve 和 reject 它們兩個也是函數,一般狀況下咱們會根據請求數據的成功和失敗來決定調用哪個。

  • 成功仍是失敗?

    • 若是是成功的,那麼一般咱們會調用resolve(messsage),這個時候咱們後續的then會被回調
    • 若是是失敗的,那麼一般咱們會調用reject(error),這個時候咱們後續的catch會被回調

OK,這就是Promise最基本的使用了。

2.2 Promise三種狀態

首先, 當咱們開發中有異步操做時, 就能夠給異步操做包裝一個Promise

異步操做以後會有三種狀態

  • pending:等待狀態,好比正在進行網絡請求,或者定時器沒有到時間。
  • fulfill:知足狀態,當咱們主動回調了resolve時,就處於該狀態而且會回調 then()
  • reject:拒絕狀態,當咱們主動回調了reject時,就處於該狀態而且會回調 catch()
new Promise((resolve,reject) => {
   setTimeout(() => {
     //resolve('Hello World')
     reject('Error Data')
   }, 1000)
 }).then(data => {
   console.log(data)
 }).catch(error => {
   console.log(error)
})

3. Promise鏈式調用

3.1 使用方式一:每一次調用都是異步操做
new Promise((resolve, reject) => {
  //1.第一次模擬網絡請求的代碼
  setTimeout(() => {
    resolve("Hello World");
  }, 2000);
}).then((data) => {
  //第一次拿到結果的處理代碼
  console.log(data); //Hello World
  return new Promise((resolve, reject) => {
    //第二次模擬網絡請求的代碼
    setTimeout(() => {
       resolve(data + " 111");
    }, 2000);
  }).then((data) => {
    //第二次拿到結果的處理代碼
    console.log(data); //Hello World 111
    return new Promise((resolve, reject) => {
       //第三次模擬網絡請求的代碼
       setTimeout(() => {
         resolve(data + "222");
       }, 2000);
    }).then((data) => {
       //第三次拿到結果的處理代碼
       console.log(data); //Hello World 111222
       return new Promise((resolve, reject) => {
         //第四次模擬網絡請求錯誤的代碼
         setTimeout(() => {
           reject(data + "error");
         }, 2000);
       }).then((data) => {
          //這裏沒有輸出,這部分代碼不會執行
          console.log(data);
          return new Promise((resolve, reject) => {
            setTimeout(() => {
               resolve(data + "333");
            }, 2000);
          });
       }).catch((data) => {
          //第四次拿到結果的處理代碼
          console.log(data); //Hello World 111222error
          //第五次模擬網絡請求的代碼
          return new Promise((resolve, reject) => {
            setTimeout(() => {
               resolve(data + "444");
            }, 2000);
          }).then((data) => {
             //第五次拿到結果的處理代碼
             console.log(data); //Hello World 111222error444
             //..不能再套娃了
          });
        });
    });
  });
});
//注意:其實reject是可選的,當咱們不用的時候能夠只寫 resolve => {}
3.2 使用方式二:只有第一次調用是異步操做

只有第一次調用是異步操做,後面的調用不是異步操做可是咱們但願後面的調用也是分層的

new Promise((resolve,reject) => {
  setTimeout(() => {
     resolve('Hello World')
  }, 1000)
}).then(data => {
  console.log(data) //Hello World
  return Promise.resolve(data + ' 111')
}).then(data => {
  console.log(data) //Hello World 111
  return Promise.resolve(data + '222')
}).then(data => {
  console.log(data) //Hello World 111222
  return Promise.reject(data + 'error')
}).then(data => {
  console.log(data) 
  return Promise.resolve(data + '333')
}).catch(data => {
  console.log(data) //Hello World 111222error
  return Promise.resolve(data + ' 444')
}).then(data => {
  console.log(data) //Hello World 111222error444
})

這裏咱們直接經過Promise包裝了一下新的數據,將Promise對象返回了

  • Promise.resovle():將數據包裝成Promise對象,而且在內部回調resolve()函數

  • Promise.reject():將數據包裝成Promise對象,而且在內部回調reject()函數

鏈式調用簡寫

簡化版代碼:若是咱們但願數據直接包裝成Promise.resolve,那麼在then中能夠直接返回數據
注意下面的代碼中我將return Promise.resovle(data)改爲了return data結果依然是同樣的

new Promise((resolve,reject) => {
  setTimeout(() => {
     resolve('Hello World')
  }, 1000)
}).then(data => {
  console.log(data) //Hello World
  return data + ' 111'
}).then(data => {
  console.log(data) //Hello World 111
  return data + '222'
}).then(data => {
  console.log(data) //Hello World 111222
  return Promise.reject(data + 'error')
}).then(data => {
  console.log(data) 
  return data + '333'
}).catch(data => {
  console.log(data) //Hello World 111222error
  return data + ' 444'
}).then(data => {
  console.log(data) //Hello World 111222error444
})

4. Promise的all方法使用

4.1 案例

假設有兩個網絡請求,咱們必需要保證兩個網絡請求都成功後才能執行一些操做。即兩個網絡請求加上後續的操做纔是一個完整的業務。怎麼實現呢?

之前的實現方式

//兩個flag
let isResult1 = false;
let isResult2 = false;
//第一個請求
$.ajax({
    url:'url1'
    success: () => { 
    	console.log("結果一");
    	isResult1 = true
    	handleResult()
	}
})
//第二個請求
$.ajax({
    url:'url2'
    success: () => { 
    	console.log("結果二");
    	isResult2 = true
    	handleResult()
	}
})

function handleResult() {
    if(isResult1 && isResult2) {
        //後續操做
    }
}
4.2 all方法的使用
Promise.all([
    new Promise((resolve,reject) => {
        //模擬網絡請求一
        setTimeout(() => {
            resolve('result1');
        },1000)
    }),
    new Promise((resolve,reject) => {
        //模擬網絡請求二
        setTimeout(() => {
            resolve('result2');
        },5000)
    }),
]).then(results => {
    //5秒後纔會打印
    console.log(results[0]); //結果一
    console.log(results[1]); //結果二
})
相關文章
相關標籤/搜索