Javascript異步編程能夠算是JS的難點之一。下面就異步編程方法之一的Promise進行詳細介紹和總結。但說到Promise以前,我會簡單提一下什麼是JS異步和回調函數。javascript
JS異步是指在進行某些須要耗時不會當即返回結果的操做時,不會阻塞後面的操做,一旦該耗時的操做完成時,則會通知須要調用其結果的函數來作後續處理。這是一種異步非阻塞的操做,也就是說任務的排列順序和執行任務是不一致的。java
和同步操做不一樣,異步操做即不會當即返回結果的操做(如發起網絡請求,下載文件,操做數據庫等)。若是咱們後續的函數須要以前返回的結果,又怎樣使以前的異步操做在其完成時通知到後續函數來執行呢?git
一般,咱們能夠將這個函數先定義,存儲在內存中,將其當作參數傳入以前的異步操做函數中,等異步操做結束,就會調用執行這個函數,這個函數就叫作回調函數(callback)。github
若是不用callback,因爲js會當即執行後面console.log
,致使打印出來的photo
爲undefined
:數據庫
var photo = downloadPhoto('http://coolcats.com/cat.gif')
console.log(photo) //undefined
複製代碼
使用callback時,咱們將handlePhoto
當作callback傳入downloadPhoto
這個異步函數中,那麼當圖片下載行爲結束後,不管是成功仍是失敗,都會執行到handlePhoto
,對photo
或者是error
進行處理。編程
downloadPhoto('http://coolcats.com/cat.gif', handlePhoto)
function handlePhoto (error, photo) {
if (error) console.error('Download error!', error)
else console.log('Download finished', photo)
}
console.log('Download started')
複製代碼
那假如callback函數一樣是個異步函數,且callback裏又嵌入了callback呢? 如此一來,嵌套太深容易引起「回調地獄
」,即代碼只會橫向發展,很差管理。json
爲此,對於那些須要連續執行的異步操做,Promise能夠是一種很好的解決辦法。api
Promise的概念對於初學者來講一直很抽象,咱們能夠舉個例子: 好比你是個經銷商,你要去工廠定貨,拿到貨後你才能本身銷售。那麼你和工廠以前立下一個契約,保證工廠在在完成生產後通知你,或者就算是因某種緣由出錯了而沒法生產也會通知到你。那麼此時這裏的契約就至關於咱們要講述的promise,promise就像是個特殊的對象,鏈接了工廠的生產行爲和你的消費行爲,是生產者和消費者間的紐帶。promise
Promise建立時,會傳給promise一個稱爲excutor
執行器的函數。這個excutor
咱們能夠理解爲生產者的生產過程函數。這個函數含有兩個參數resolve
和reject
,這倆參數也一樣是函數,用來傳遞異步操做的結果。語法以下:bash
let promise = new Promise(function(resolve, reject) {
// executor
})
複製代碼
有幾點值得說一下:
excutor
會當即執行。resolve
和reject
是JS引擎自動建立的函數,咱們無需本身建立,只需將其做爲參數傳入就好。promise
的內部狀態是個對象,初始時爲:{
state, //pending
result, //undefined
}
複製代碼
一旦exucutor
執行完,要麼產生value
,要麼產生error
,此時會當即調用resolve
(當產生value
時)或者調用reject
(當產生error
)時,內部狀態也會隨之改變,以下圖所示:
注意,當excutor
裏面即便調用了多個resolve
和reject
,其最終仍是隻執行一個,其餘的都被忽略掉。
let promise = new Promise(function(resolve, reject) {
resolve("done");
reject(new Error("…")); // ignored
setTimeout(() => resolve("…")); // ignored
});
複製代碼
在上面例子中,既然生產者的行爲完成了,結果也傳遞出去了,那麼如何通知消費者呢?咱們可使用.then
, .catch
, 和.finally
來註冊消費者的函數,把.then
,.catch
,.finally
看作是個訂閱列表,將消費者的函數註冊於此,一旦收到結果時,就能夠通知到對方進行相應的處理。
promise.then(
result = > resultHandle(result)
error = > errorHandle(error)
)
複製代碼
.then()
接收2個函數,一個用來處理正常結果result
,一個用來處理error
,但通常狀況下,咱們也能夠不用在.then()
中傳入這個error
處理的函數。
promise.catch(
error = > errorHandle(error)
)
複製代碼
其實這就是至關於promise.then(null, errorHandle)
。 .catch
會捕獲到整個異步操做中,或者是一系列連續的異步操做鏈中的出現的任何類型的錯誤,一旦拋出錯誤,則會直接轉入到.catch
中進行錯誤處理。
promise.finally(
finalHandle
)
複製代碼
當promise的狀態肯定時,即不管拿到的是正常結果仍是錯誤信息,總會執行這個finalHandle
函數。用.finally
能夠作一些清理操做,好比發起網絡請求後,能夠中止loading顯示。
當有一系列的異步操做須要一個接一個執行時,可使用promise的調用鏈。 舉個例子,咱們用fetch
這個方法去發起網絡請求,fetch()
返回的是一個promise對象,那麼咱們能夠對其連續地調用.then
來進行一步步連續地異步操做。注意:promise.then()
返回的是一個新的promise對象,因此咱們才能夠繼續對其調用.then
。
// Make a request for user.json
fetch('/article/promise-chaining/user.json')
// Load it as json
.then(response => response.json())
// Make a request to github
.then(user => fetch(`https://api.github.com/users/${user.name}`))
// Load the response as json
.then(response => response.json())
// Show the avatar image (githubUser.avatar_url) for 3 seconds (maybe animate it)
.then(githubUser => {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => img.remove(), 3000); // (*)
}).catch(error => console.log(error));
複製代碼
這段代碼的做用就是先發起網絡請求獲取到服務端的相應內容(其實只是響應頭),而後經過調用response.json()
繼續獲取response
完整的遠程數據並將其解析爲JSON格式(也是異步操做),接着根據json中user的name信息,繼續發起網絡請求,拿到用戶object及其頭像url,展現其頭像並在3秒後刪除頭像圖片。.catch
會處理上面一系列流程中出現的任何錯誤。
值得注意的是,promise.then( handleFunction )
中的handleFunction
能夠返回當即值,也能夠返回promise
對象。若是返回當即值,則能夠直接把結果傳入到下一步的.then
進行處理,可是若是返回的是promise
對象,那麼必定要等到這個返回的promise
處理完,拿到結果後,纔會進行下一步的.then
處理!能夠用下圖加以理解:
在JS異步編程中,Promise相對於callback,具備更優的代碼流,而且具備很好的靈活性。Promise符合天然的事物執行順序,即先作異步操做,而後再用.then
告知下一步該作什麼。而在Callback的用法中,先得知道下一步作什麼,而後才能將其做爲callback函數傳入異步操做函數中。並且,promise在獲得結果後,能夠通知到多個後續的結果處理函數,.then
就像一個訂閱列表同樣。而在callback的用法中,只能傳入1個callback函數。
Promise: javascript.info/promise-bas…
Promise MDN文檔: developer.mozilla.org/en-US/docs/…
Callback Hell:callbackhell.com/