ES6中一個很是重要和好用的特性就是Promise類。javascript
可是初次接觸Promise會一臉懵逼,這TM是什麼東西?看看官方或者一些文章對它的介紹和用法也是一頭霧水。java
Promise究竟是作什麼的呢? => Promise是異步編程的一種解決方案。通常狀況下有異步操做時,使用Promise對這個異步操做進行封裝。ajax
那何時咱們會來處理異步事件呢?一種很常見的場景應該就是 網絡請求 了。編程
咱們封裝一個網絡請求的函數不能當即拿到結果,因此不能像簡單的 3+4=7 同樣將結果返回。因此咱們每每會傳入另一個函數,在數據請求成功時將數據經過傳入的函數回調出去。若是隻是一個簡單的網絡請求,那麼這種方案不會給咱們帶來很大的麻煩。可是當網絡請求很是複雜時就會出現回調地獄。promise
OK,我以一個很是誇張的案例來講明。咱們來考慮下面的場景(有誇張的成分):服務器
$.ajax('url1',function(data1) { $.ajax(data1['url2'],function(data2) { $.ajax(data2['url3'],function(data3) { $.ajax(data3['url4'],function(data4) { console.log(data4); }) }) }) })
上面的代碼有什麼問題嗎?正常狀況下不會有什麼問題,能夠正常運行而且獲取咱們想要的結果。可是這樣額代碼難看並且不容易維護,咱們更加指望的是一種更加優雅的方式來進行這種異步操做。如何作呢?就是使用Promise,Promise能夠以一種很是優雅的方式來解決這個問題。網絡
咱們先來看看Promise最基本的語法。異步
這裏咱們用一個定時器來模擬異步事件:假設下面的data是從網絡上1秒後請求的數據,console.log就是咱們的處理方式。異步編程
setTimeout(() => { console.log('Hello World'); },1000)
上面是咱們過去的處理方式,咱們將它用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最基本的使用了。
首先, 當咱們開發中有異步操做時, 就能夠給異步操做包裝一個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) })
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 => {}
只有第一次調用是異步操做,後面的調用不是異步操做可是咱們但願後面的調用也是分層的
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 })
假設有兩個網絡請求,咱們必需要保證兩個網絡請求都成功後才能執行一些操做。即兩個網絡請求加上後續的操做纔是一個完整的業務。怎麼實現呢?
之前的實現方式
//兩個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) { //後續操做 } }
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]); //結果二 })