Promise
對象是一個構造函數,用來生成 Promise
實例。Promise
構造函數接受一個函數做爲參數,該函數的兩個參數分別是 resolve
和 reject
。es6
resolve
函數的做用是,在異步操做成功時調用(Promise
對象的狀態從 pending
變爲 fulfilled
),並將異步操做的結果,做爲參數傳遞出去。json
reject
函數的做用是,在異步操做失敗時調用(Promise
對象的狀態從 pending
變爲 rejected
),並將異步操做報出的錯誤,做爲參數傳遞出去。api
const funPromise = function(options) { return new Promise(function(resolve, reject) { if (/* 異步操做成功 */){ resolve(result); } else { reject(error); } }); }
resolve
函數的參數除了正常的值之外,還多是另外一個 Promise
實例,此時,初始 promise
的最終狀態根據傳入的新的 Promise
實例決定。數組
reject
方法的做用,至關於拋出錯誤。等同於 throw new Error('error')
。promise
Promise
實例具備 then
方法,它的做用是爲 Promise
實例添加狀態改變時的回調函數,即 Promise
實例生成之後,用 then
方法分別指定 fulfilled
狀態和 rejected
狀態的回調函數。併發
funPromise().then(function(result) { // fulfilled }, function(error) { // rejected })
then
方法能夠接受兩個回調函數做爲參數。第一個回調函數是 Promise
對象的狀態變爲 fulfilled
時調用,第二個回調函數是 Promise
對象的狀態變爲 rejected
時調用。其中,第二個函數是可選的,不必定要提供。這兩個函數都接受 Promise
對象傳出的值做爲參數。app
then
方法返回的是一個新的 Promise
實例(注意,不是原來那個 Promise
實例)。所以能夠採用鏈式寫法,即 then
方法後面再調用另外一個 then
方法來處理上一個 then
方法中 return
的結果。異步
funPromise().then(function(result) { return result.data; }).then(function(data) { // fulfilled });
上面的代碼使用 then
方法,依次指定了兩個回調函數。第一個回調函數完成之後,會將返回結果做爲參數,傳入第二個回調函數。而且,第一個 then
返回的結果也能夠是另外一個異步操做的 Promise
對象,這時後一個 then
函數,就會等待該 Promise
對象的狀態發生變化,纔會被調用。async
funPromise().then( (result) => { return funPromise(result); } ).then( (data) => { /* fulfilled */ }, (error) => { /* rejected */ } );
上面代碼中,第一個 then
方法指定的回調函數,返回的是另外一個 Promise
對象。這時,第二個 then
方法指定的回調函數,就會等待這個新的 Promise
對象狀態發生變化。若是變爲 fulfilled
,就調用第一個回調函數,若是狀態變爲 rejected
,就調用第二個回調函數。函數
Promise
實例具備 catch
方法,它的做用是爲 Promise
實例添加狀態改變爲 rejected
狀態的回調函數,也就是 then
方法的第二個函數的替代寫法。
funPromise().then(function(result) { // fulfilled }).catch(function(error) { // 處理 funPromise 和以前 then 回調函數運行時發生的錯誤 });
Promise
對象的錯誤具備「冒泡」性質,會一直向後傳遞,直到被捕獲爲止。也就是說,不管前面有多少個 then
函數,其中的錯誤老是會被下一個 catch
語句捕獲。
funPromise().then(function(result) { return funPromise(result); }).then(function(data) { // fulfilled }).catch(function(error) { // 處理前面三個 Promise 產生的錯誤 });
通常來講,不要在 then
方法裏面定義 rejected
狀態的回調函數(即 then
的第二個參數),老是使用 catch
方法,由於這種寫法能夠捕獲前面 then
方法執行中的錯誤。
catch
方法返回的仍是一個 Promise
對象,而且 catch
中若是沒有拋出任何其它錯誤,那麼該 Promise
對象則是 resolved
狀態。並且後面還能夠接着調用 then
方法,可是前面的 catch
不能捕獲後面的 then
中的錯誤,因此儘可能 catch
都寫在最後。
Promise.all()
方法用於將多個 Promise
實例,包裝成一個新的 Promise
實例。其接受一個數組做爲參數,數組中的值都是 Promise
實例,若是不是,就會先調用 Promise.resolve()
方法,將參數轉爲 Promise
實例,再進一步處理。
const p = Promise.all([funPromise(1), funPromise(2), funPromise(3)]);
p
的狀態由數組中的值決定,分紅兩種狀況。
Primise
實例的狀態都變成 fulfilled
,p
的狀態纔會變成 fulfilled
,此時數組中實例的返回值組成一個數組,傳遞給 p
的回調函數。rejected
,p
的狀態就變成 rejected
,此時第一個被 reject
的實例的返回值,也就是報錯信息,會傳遞給 p
的回調函數。p.then(function (results) { // 所有 fulfilled,results 是個數組,裏面是每一個實例的返回結果 }).catch(function(error){ // 其中有一個變爲 rejected });
注意,若是做爲參數的 Promise
實例,本身定義了 catch
方法,那麼它一旦被 rejected
,並不會觸發 Promise.all()
的 catch
方法。
用 Promise
對象實現 Ajax
。
const getAjax = function(url) { const promise = new Promise(function(resolve, reject){ const handler = function() { if (this.readyState === 4 && this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } }; const xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.onreadystatechange = handler; xhr.responseType = "json"; xhr.setRequestHeader("Accept", "application/json"); xhr.send(); }); return promise; }; getAjax("/test.json").then(function(json) { console.log('Contents: ' + json); }, function(error) { console.error('出錯了', error); });
當 async
函數執行的時候,一旦遇到 await
就會先等到 await
後的異步操做完成,再接着執行函數體內以後的語句。
async
函數返回一個 Promise
對象,可使用 then
方法添加回調函數。async
函數內部 return
語句返回的值,會成爲 then
方法回調函數的參數。
async function f() { return 'hello dora'; } f().then(v => console.log(v)) // "hello dora"
async
函數內部拋出錯誤,會致使返回的 Promise
對象變爲 rejected
狀態。拋出的錯誤對象會被 catch
方法回調函數接收到。
async function f() { throw new Error('出錯了'); } f().catch( e => console.log(e)) // Error: 出錯了
正常狀況下,await
命令後面是一個 Promise
對象,返回該對象的結果。若是不是 Promise
對象,就直接返回對應的值。
async function f() { return await 123; // 等同於 return 123; } f().then(v => console.log(v)) // 123
await
命令後面的 Promise
對象若是變爲 rejected
狀態,則錯誤會被 catch
方法的回調函數接收到。
任何一個 await
語句後面的 Promise
對象變爲 rejected
狀態,那麼整個 async
函數就會中斷執行。
有時,咱們但願即便前一個異步操做失敗,也不要中斷後面的異步操做,有兩個解決辦法:
第一種方法是能夠將 await
放在 try...catch
結構裏面,這樣無論這個異步操做是否成功,後面的代碼都會執行。
async function f() { try { await Promise.reject('出錯了'); } catch(e) { } return await Promise.resolve('hello dora'); } f().then(v => console.log(v)) // hello dora
另外一種方法是 await
後面的 Promise
對象再跟一個 catch
方法,處理前面可能出現的錯誤。
async function f() { await Promise.reject('出錯了').catch(e => console.log(e)); return await Promise.resolve('hello dora'); } f().then(v => console.log(v)) // 出錯了 // hello dora
1. 錯誤處理
前面已經說過,await
命令後面的 Promise
對象,運行結果多是 rejected
,因此防止出錯的方法,就是最好把 await
命令放在 try...catch
代碼塊中。若是有多個 await
命令,能夠統一放在 try...catch
結構中,若是隻有一個 await
,可使用上例中的 catch
捕獲 await
後面的 promise
拋出的錯誤。
const superagent = require('superagent'); const NUM_RETRIES = 3; async function test() { let i; for (i = 0; i < NUM_RETRIES; i++) { try { await superagent.get('/api/xxx'); break; } catch(err) {} } } test();
上面代碼中,使用 try...catch
結構,實現屢次重複嘗試。若是 await
操做成功,就會使用 break
語句退出循環;若是失敗,會被 catch
語句捕捉,而後進入下一輪循環。
2. 多個 await 異步操做併發執行
多個 await
命令後面的異步操做,若是不存在繼發關係(即互不依賴),最好讓它們同時觸發,以縮短程序的執行時間。
// 寫法一 let [foo, bar] = await Promise.all([getFoo(), getBar()]); // 寫法二 let fooPromise = getFoo(); let barPromise = getBar(); let foo = await fooPromise; let bar = await barPromise;
3. forEach 等數組遍歷方法的參數爲 async 函數時是併發執行的
只有 async
函數內部是繼發執行,外部不受影響,所以 forEach()
、map()
等數組遍歷方法的參數改爲 async
時是併發執行的。
function dbFuc() { //這裏不須要 async let docs = [{}, {}, {}]; // 會獲得錯誤結果 docs.forEach(async (doc)=> { await funPromise(doc); }); }
上面代碼會獲得錯誤結果,緣由是這時三個 funPromise(doc)
操做是併發執行的,也就是同時執行,而不是繼發執行。所以正確的寫法是採用 for
循環。
async function dbFuc() { let docs = [{}, {}, {}]; for (let doc of docs) { await funPromise(doc); } }
若是須要併發執行,可以使用 Promise.all()
方法。
async function dbFuc() { let docs = [{}, {}, {}]; let promises = docs.map((doc) => funPromise(doc)); let results = await Promise.all(promises); return results; }
有一組異步操做,須要按照順序完成。
async function logInOrder(urls) { // 併發讀取遠程URL const textPromises = urls.map(async url => { const response = await fetch(url); return response.text(); }); // 按次序輸出 for (const textPromise of textPromises) { console.log(await textPromise); } }
上面代碼中,雖然 map
方法的參數是 async
函數,但它是併發執行的,由於只有 async
函數內部是繼發執行,外部不受影響。後面的 for..of
循環內部使用了 await
,所以實現了按順序輸出。
參考連接:
Promise 對象
async 函數