Promise是異步編程的一種解決方案,比傳統的解決方案-回調函數和事件-更合理更強大。簡單講,Promise裏儲存着某個將來纔會結束的事件的結果,從它能夠獲取異步操做的消息。 Promise對象有如下兩個特色:javascript
Promise能夠方便的將異步操做以同步操做的流程表達出來,避免層層嵌套的回調函數。java
先建立一個Promise實例:編程
const promise = new Promise((resolve, reject) { // ...some code if(/*異步操做完成*/) { resolve(value) else { reject(error) } }) 複製代碼
Promise構造函數接收一個函數做爲參數,該函數的兩個參數分別是resolve函數和reject函數。resolve函數的做用是將Promise的狀態從pending變成resolved,在異步操做成功時調用,並將結果做爲參數傳遞出去;reject函數的做用時將Promsie的狀態從pending變成rejected,在異步操做失敗時調用,並將異步操做的錯誤做爲參數傳遞出去。json
Promise的then方法能夠分別指定Resolved狀態和rejected狀態的回調函數。其中第一個回調做爲Resolved狀態的回調,第二個回調做爲rejected狀態的回調,而第二個回調是可選的。數組
下面咱們來猜一猜下面的代碼的打印順序是什麼樣的:promise
let promise = new Promise((resolve, reject)=>{ console.log('Promise'); resolve() }) promise.then(() => { console.log('Resolved'); }) console.log('Hi') 複製代碼
讓咱們先來推理一下:首先,Promise構造函數中的代碼會在新建的時候當即執行,因此,第一個打印的應該是「Promise」;其次then方法指定的回調函數會在當前腳本全部同步任務執行完以後才執行,因此「Resolved」應該是最後打印的。總上所述。打印順序應該是bash
// Promise // Hi // Resolved 複製代碼
還有一點須要注意:resolve和reject並不會阻止後面代碼的執行,而且後面的代碼還會先執行markdown
new Promise((resolve, reject) => { resolve(1); console.log(2); }).then((res) => { console.log(res) }) // 2 // 1 複製代碼
這是由於執行resolved是在本次事件循環的末尾執行,老是晚於本次循環的同步任務。 通常來講,不該該在resolve或reject以後寫代碼,應該將其放在then方法裏面。爲了預防萬一,咱們能夠在resolve和reject前加上return語句。異步
請看下面的例子,來猜一猜最後會進入then仍是catch呢:異步編程
const p1 = new Promise((resolve, reject) => { setTimeout(() => reject(new Error('fail')), 3000) }) const p2 = new Promise((resolve, reject) => { setTimeout(()=> resolve(p1), 1000) }) p2.then((res)=> {console.log(res)}).catch((err) => {console.log(err)}); // Error:fail 複製代碼
答案是會進入catch。這是由於p1的狀態會傳遞給p2,也就是說p1的狀態決定了p2的狀態,若是p1是pending那麼p2的回調會等待p1的狀態改變;若是p1的狀態是resolved或rejected,那麼p2的回調會當即執行。因此上面的代碼會在3秒後觸發catch指定的回調。
then方法的做用是爲Promise實例添加狀態改變時的回調函數。它的第一個參數是resolved狀態的回調函數,第二個參數(可選)是rejected狀態的回調函數。
注意:then方法返回的是一個新的Promise實例(注意不是原來的Promise實例)。所以能夠採用鏈式寫法,即.then方法後面再調另外一個.then。而且上一個then方法返回的結果會做爲回調方法的參數。
採用鏈式的then方法能夠依次指定一組按照順序調用的回調函數,而且前一個回調函數可能返回的仍是一個Promise實例,然後面的回調函數則會等待該Promise對象狀態發生改變時再被調用。看下面代碼你就懂了:
getJSON('/posts.json') .then(post => getJSON(post.commentURL)) .then( comments => {console.log('resolved', comments)}, err => {console.log('rejected', err)} ) 複製代碼
上面的代碼getJSON返回的時一個Promise對象。因此第一個then返回的就是一個Promise,此時第二個then方法指定的回調函數就會等待這個新的Promise對象狀態發生改變時,resolved就調用第一個回調,rejected就會調用第二個回調。
catch方法是用於指定發生錯誤時的回調函數。注意它不止能夠捕獲Promise拋出的錯誤還能捕獲前一個回調函數運行時發生的錯誤。
getJSON('/posts.json').then(res => { throw new Error('error') }).catch(err => { console.log(err) }) // Error error 複製代碼
上面的代碼中,getJSON這個Promise對象的狀態若爲resolved,會進入then方法,若爲rejected則會進入catch方法。另外,若是then方法指定的回調在運行中拋出錯誤也會被catch捕捉。
Promise對象的錯誤具備'冒泡'的性質,會一直向後傳遞,直到被捕獲,也就是說錯誤老是會被下一個catch語句捕獲。
getJSON('/posts.json').then(post => { return getJSON(post.commentURL) }) .then(comments => { // somecode }).catch(err => { // 處理前面三個Promise產生的錯誤 }) 複製代碼
咱們通常使用catch捕獲錯誤,而不使用then方法的第二個參數。
須要注意的是,catch方法返回的仍是一個Promise對象,所以後面還能接着調用then方法。
若沒有指定catch方法處理錯誤,Promise對象拋出的錯誤不會傳遞到外層代碼,即不會有任何反應。
Promise.all方法用於將多個Promise實例包裝成一個新的Promise實例。Promise.all方法接收的參數必須具備Iterator接口且返回的每一個成員都必須是Promise實例。若不是Promise實例,Promise.all方法內部就會先執行Promise.resolve方法將參數轉爲Promise實例。
let p = Promise.all([p1,p2,p3]) 複製代碼
上面代碼中,p的狀態由p1,p2,p3決定,分爲兩種狀況:
一、只有p一、p二、p3的狀態都是Fulfilled,p的狀態纔會是Fulfilled,此時,p一、p二、p3的返回值組成一個數組,傳遞給p的回調函數。
二、只要p一、p二、p3任一個狀態爲rejected,則p的狀態爲rejected,返回第一個被rejected的實例的返回值給p的回調函數。
看下面的例子:
const promises = [1,2,3].map(id => { return getJSON('/post' + id + '.json') }); Promise.all(promises).then((res) => { // ... }).catch(err => { // ... }) 複製代碼
若是做爲參數的Promise實例自身定義了catch方法,那麼它被rejected時並不會觸發Promise.all的catch方法。 看下面例子:
const p1 = new Promise((resolve,reject) => { resolve('hello') }).then((res) => res) .catch(e => e); const p2 = new Promise((resolve, reject) => { throw new Error('error!') }).then(res => res) .catch(err => err); Promise.all([p1,p2]) .then(res => console.log(res)) .catch(err => {console.log(err)}) // ["hello", Error: error!] 複製代碼
上面的代碼中p2會rejected,可是Promise.all()會進入then的回調,這是由於p2有本身的catch方法,而該方法返回的是一個新的Promise實例,p2實際上指向的是這個實例,而這個實例執行完以後也會變成resolved,因此all方法中的兩個實例都是resolved,所以不會調用catch方法。
race方法一樣是將多個Promise實例包裝成一個新的實例。
let p = Promise.race([p1,p2,p3]) 複製代碼
不一樣之處是race方法的狀態在第一個參數實例率先改變狀態後,p的狀態就會跟着改變,並將那個率先改變的實例的返回值傳遞給p的回調。同all方法同樣,若參數中有不是Promise實例的,會先調用resolve方法,將參數轉換成Promise實例再進一步處理。
用下面的例子介紹下race的使用場景:
const p = Promise.race([ fetch('/resource-that-may-take-a-while'), new Promise((resolve, reject) { setTimeout(() => reject(new Error('request timeout')), 5000) }) ]) p.then(res => console.log(res)); p.catch(err => console.log(err)); 複製代碼
上面的代碼中,若是5秒內fetch方法無返回結果,則p的catch回調就會被觸發。
有時候須要將現有的對象轉換成Promise.resolve對象,這時就要用到Promise.resolve方法。
Promise.resolve('foo'); // 等價於 new Promise(resolve => resolve('foo')) 複製代碼
Promise.resolve方法的參數分紅如下四種狀況: 一、Promise實例
若是參數時Promise實例,那麼Promise.resolve將不做任何修改,直接返回這個實例。
二、thanable對象
thanable對象是指具備then方法的對象,好比下面這個:
let thenable = { then: (resolve, reject) => { resolve(42) } } 複製代碼
Promise.resolve方法會將這個對象轉換成Promise對象,並當即執行thenable對象的then方法。
三、不具備then方法的對象或者根本不是對象
若是參數是一個原始值或者不具備then方法的對象,那麼Promise.resolve返回的是一個新的Promise對象,狀態爲rejected,Promise.resolve方法的參數會同時傳給回調函數,而且回調會當即執行。
四、不帶任何參數 不帶任何參數的話,resolve會直接返回一個帶有resolved狀態的Promise對象,須要注意的是,當即resolved的Promise對象是在本輪事件循環結束時執行,而不是在下一輪事件循環開始時執行。
Promise.reject方法也會返回一個新的Promise實例,狀態爲Rejected。與Promise.resolve方法不一樣的一點是,reject方法的參數會原封不動的做爲reject的理由變成後續方法的參數。以下:
const thenable = { then: (resolve, reject) => { reject('error') } } Promise.reject(thenable).catch(e => { console.log(e === thenable) }) // true 複製代碼
上面代碼中,Promise.reject方法的參數是一個thenable對象,然後面catch方法的參數不像resolve方法同樣,拋出的‘error’字符串,而是這個thenable對象自己。
這篇文章是對阮一峯老師的《ES6標準入門》的一個學習筆記吧,內容大可能是來源於書中,在此整理一下方便後續複習鞏固。對Promise有其餘想法的同窗歡迎評論指教哦。
阮一峯 《ES6標準入門》