前段時間的工作中,由於項目要在前端實現存儲,於是便使用了websql,而websql的API涉及到了很多的異步問題,如果采取回調函數的方式處理,代碼不夠優雅,而且不利於理解,於是便找到了Promise,使用之后有一些自己的理解和心得,跟大家在本文中一起分享一下。
Promise為何物?
Promise中文釋義為“誓言”、“承諾”之意,根據其音譯,那就是“普羅米修斯”,這貨很強大啊,在希臘神話中,是最具智慧的神明之一,最早的泰坦巨神后代,名字有“先見之明”(Forethought)的意思。
而JS中的Promise,其實就是ES6提供的一個對象,它扮演的就是“先知”這么個角色,它用來傳遞異步操作的消息,代表了某個未來才會知道結果的事件,並且這個事件提供統一的API,便於進一步處理,這個類目前在chrome32、Opera19、Firefox29以上的版本都已經支持。
接下來說說Promise規范,大家應該都不陌生了,因為這個規范已經出來很長一段時間了,目前在chrome32、Opera19、Firefox29以上的版本都已經支持Promise了,Promise規范的主要內容如下:
- Promise對象有三種狀態:Pending(進行中)、Resolved(已完成)、Rejected(已失敗),這三種狀態只能從Pending轉到Rejected或者Pending轉到Rejected,不能逆向轉換,而且狀態一旦轉換完成后,狀態就凝固了,不能夠再改變了。只有異步操作的結果,才能決定當前是哪一種狀態,任何其他操作都無法改變這個狀態,這點跟Deferred是不一樣的。
- Promise模式唯一需要的一個接口是調用then方法,它可以用來注冊當promise完成或者失敗時調用的回調函數,then方法接受兩個參數,第一個參數是成功時的回調,在promise由Pending態轉換到Resolved態時調用,另一個是失敗時的回調,在promise由Pending態轉換到Rejected態時調用。
Promise有啥好處?
說了這么多,Promise到底有啥好處呢?能為我們的工作帶來什么便捷呢?話不多說,是騾子是馬拉出來遛遛,我們先new一個來玩玩:
var p = new Promise(function(resolve, reject){ //用setTimeout模擬異步操作 setTimeout(function(){ console.log('第一個Promise執行完成'); resolve('第一個Promise'); }, 2000); });
上面的代碼可以看出,new一個Promise對象時,要傳入一個函數,而這個函數一般有兩個參數,一個是resolve,另一個是reject,這兩個參數不一定都要,但你要用的那個一定要傳入,所以兩個都寫上就好了,這兩個參數其實代表的是異步操作執行成功后的回調函數和異步操作執行失敗后的回調函數。而上面這段代碼的意思就是用setTimeout模仿一個異步的操作,2秒后,在控制台打印“第一個Promise執行完成”,然后調用resolve方法,帶一個參數:“第一個Promise”,運行代碼,會直接輸出“第一個Promise執行完成”,雖然我們只是new了一個Promise對象,但是函數當參數傳進去的時候會被立即執行,所以直接輸出,為了防止這種情況,我們一個將這段代碼包在一個函數中:
function test1(){ var p = new Promise(function(resolve, reject){ //做一些異步操作 setTimeout(function(){ console.log('第一個Promise執行完成'); resolve('第一個Promise'); }, 2000); }); return p; }
如上,最后返回了一個Promise對象,接下來重頭戲來了,Promise對象的then方法要出場了,它可以用來注冊當Promise完成或者失敗時調用的回調函數,我們接着上面的代碼繼續往下寫:
test1().then(function(val){ console.log(val); //后續操作 })
執行這段代碼,兩秒后,會在頁面輸出“第一個Promise執行完成”,緊接着輸出“第一個Promise”。到這里,機智的你已經看出來,這個then不就是異步處理完成后,將傳遞過來的數據取到,然后進行后續操作,就是充當一個回調函數嘛,感覺這個Promise沒有什么大不了的地方啊,只不過是把原來的回調寫法分離出來,在異步操作執行完后,用鏈式調用的方式執行回調函數而已,其實這種鏈式調用在一些比較復雜的頁面中是很有必要的,因為回調函數如果比較少還好說,一旦多了起來而且必須講究執行順序的話,回調函數開始嵌套,那代碼的惡心程度是簡直無法忍受。而Promise的出現就是為了解決這個問題的。
Promise就提供了一種優雅的解決方案,主要用法就是將各個異步操作封裝成好多Promise,而一個Promise只處理一個異步邏輯。最后將各個Promise用鏈式調用寫法串聯,在這樣處理下,如果異步邏輯之間前后關系很重的話,你也不需要層層嵌套,只需要把每個異步邏輯封裝成Promise鏈式調用就可以了。
Promise的基本API
話不多說,先用console.dir(Promise)打印出來看看:
從上圖可以看出來主要有以下幾個方法:
- Resolve:該方法可以使 Promise 對象的狀態改變成成功,同時傳遞一個參數用於后續成功后的操作。
- Reject:該方法則是將 Promise 對象的狀態改變為失敗,同時將錯誤的信息傳遞到后續錯誤處理的操作。
- Then: 所有的 Promise 對象實例里都有一個 then 方法,它是用來跟這個 Promise 進行交互的,then方法主要傳入兩個方法作為參數,一個 resolve 函數,一個 reject 函數,鏈式調用 ,上一個Promise對象變為resolved的時候,調用then中的Resolve方法,否則調用Reject方法,且then 方法會缺省調用 resolve() 函數。
- Catch:該方法是 then(onFulfilled, onRejected) 方法當中 onRejected 函數的一個簡單的寫法,也就是說也可以寫成then,但是用來捕獲異常時,用catch更加便於理解。
- All:該方法可以接收一個元素為 Promise 對象的數組作為參數,當這個數組里面所有的 Promise 對象都變為 resolve 時,該方法才會返回。就是全部都執行完了才接着往下執行,例如:
var p1 = new Promise(function (resolve) { setTimeout(function () { resolve("p1"); }, 2000); }); var p2 = new Promise(function (resolve) { setTimeout(function () { resolve("p2"); }, 2000); }); Promise.all([p1, p2]).then(function (result) { console.log(result); });
6. Race:競速,類似All方法,它同樣接收一個數組,不同的是只要該數組中的 Promise 對象的狀態發生變化(無論是 resolve 還是 reject)該方法都會返回。就是只要某一個執行完了就接着往下執行。
Promise的鏈式調用
Then的鏈式調用才是Promise的意義所在,而要實現鏈式調用,在then中的resolve方法如何return很關鍵。在then方法中通常傳遞兩個參數,一個 resolve 函數,一個 reject 函數。reject暫時不討論,就是出錯的時候運行的函數罷了。resolve 函數必須返回一個值才能把鏈式調用進行下去,而且這個值返回什么是有很大講究的。
-
在then的resolve方法中返回一個新的Promise對象,如下代碼:
function test1(){ var p = new Promise(function(resolve, reject){ //做一些異步操作 setTimeout(function(){ console.log('第一個Promise執行完成'); resolve('第一個Promise傳來的值'); }, 2000); }); return p; } test1().then(function(val){ console.log('進入第一個then'); console.log(val); var p1 = new Promise(function(resolve, reject){ setTimeout(function(){ console.log('第一個then執行完成'); resolve('第二個Promise傳來的值'); }, 2000); }) return p1; }).then(function(val){ console.log('進入第二個then'); console.log(val); })
打印結果如下:
上述例子通過鏈式調用的方式,按順序打印出了相應的內容。then 可以使用鏈式調用的寫法原因在於,每一次執行該方法時總是會返回一個 Promise 對象,后一個then中的resolve方法要等到前一個then處理完了才執行。而返回一個新Promise之后再調用的then就是新Promise中的邏輯了。另外,在 then onFulfilled 的函數當中的返回值,可以作為后續操作的參數。
-
在then的resolve方法中返回一個值,如下代碼:
function test1(){ var p = new Promise(function(resolve, reject){ //做一些異步操作 setTimeout(function(){ console.log('第一個Promise執行完成'); resolve('第一個Promise傳來的值'); }, 2000); }); return p; } test1().then(function(val){ console.log('進入第一個then'); console.log(val); setTimeout(function(){ console.log('第一個then執行完成'); }, 2000); return '第二個Promise傳來的值'; }).then(function(val){ console.log('進入第二個then'); console.log(val); })
打印結果如下:
可以看出,第一個then中的resolve還未執行完,就進入了第二個then中,沒有進行等待,是因為第一個then中返回的是一個字符串,而不是Promise對象,返回的值會傳遞到下一個then的resolve方法參數中。
總結
以上就是ES6 Promise的基本概念和用法,Promise還是非常贊的,如果你還在使用回調模式,我強烈建議你切換到 Promise,這樣你的代碼會變的更少,更優雅,並且更加容易理解,這樣就不會等你開發完后,被那些讀你代碼的人在背后偷偷罵你。本文我自己理解結合網上的一些資料,希望你看完之后對你有幫助,有不足之處歡迎指正,謝謝~