簡單介紹下這幾個的關系
為方便起見 用以下代碼為例簡單介紹下這幾個東西的關系,
async function buildData(name) {
try {
let response1 = await axios.get('/api/user?name=' + name);
let userInfo = response1.data;
let response2 = await axios.get('/api/topics?user_id' + userInfo._id);
let posts = response2.data;
// i got it.
} catch(err) {
console.log(err);
}
}
buildData('xiaoming');
async
在函數聲明前使用async關鍵詞修飾 說明函數中有異步操作
await
等待 后面的代碼執(zhí)行完畢 再繼續(xù)向下執(zhí)行
promise
Promise 是一個對象,從它可以獲取異步操作的消息,知道異步函數是完成了還是出錯了。
axios返回的結果就是一個promise
try catch
try catch JavaScript的異常捕獲機制,凡是在try語句塊中的代碼出錯了,都會被catch捕獲。
上面的代碼就是說
- buildData 這個函數被 async 修飾 說函數中有異步操作
- await 等待異步操作結果
- 如果有錯誤發(fā)生 使用try catch 捕獲異常
上面說的太過簡單,簡要的說明下 各個東西是干啥的。這里的核心是 promise 下面會逐個介紹
Promise
Promise字面上講,是一個承諾。這個承諾有三個狀態(tài)
- pending 進行中(懸而未決)
- fulfilled 已成功(以滿足)
- rejected 已失敗(已拒絕)
從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統(tǒng)一的 API,各種異步操作都可以用同樣的方法進行處理。
Promise對象有以下兩個特點:
- 對象的狀態(tài)不受外界的影響。只有異步操作的結果可以決定當前是哪種狀態(tài)。
- 狀態(tài)一旦改變 就不會再變,任何時候都可以得到這個結果。promise對象狀態(tài)的改變只有兩種情況,
- pending 到 fulfilled
- pending 到 rejected
只要這兩種情況發(fā)生,狀態(tài)就凝固了,不會再改變了,會一直保持這個結果,這時就定型了 resolved。
如果改變已經發(fā)生了,任何時候添加回調函數,得到的都是這個結果。
因為創(chuàng)建Promise對象時,回調函數中有resolve和reject兩個參數,后續(xù)的resolved統(tǒng)一指的是fulfilled狀態(tài),不包括rejected狀態(tài)
一個Promise對象一旦狀態(tài)確定了,它的使命也就結束了。后面的代碼都不應該再執(zhí)行了,最好return resolve();
如果狀態(tài)已經resolve了,再在后面拋出錯誤也是無效的,也不會改變狀態(tài)為rejected,后面有異常也不會拋出。
Promise對象如何知道異步操作結果又如何傳遞
Promise對象如何知道異步操作的結果呢,那就是回調函數了,一個表示成功resolve,一個表示失敗reject,
ES6 規(guī)定,Promise對象是一個構造函數,用來生成Promise實例。
下面代碼創(chuàng)造了一個Promise實例。
var promise = new Promise(function(resolve, reject) {
// ... some code
console.log(我一創(chuàng)建就執(zhí)行了)
if (/* 異步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Promise新建后,就會立即執(zhí)行,返回一個Promise對象。
Promise構造函數需要一個函數作為參數,這個函數有兩個參數,分別是resolve和reject,
它們是兩個函數,由 JavaScript 引擎提供,不用自己部署
- resolve: 把Promise狀態(tài) 從 pending 變?yōu)?resolved ,在異步操作成功時調用,并將異步操作的結果,作為參數傳遞出去
- reject: 把Promise狀態(tài) 從 pending 變?yōu)?reject ,在異步操作失敗時調用,并把錯誤作為參數傳遞出去
此外,reject方法的作用,也等同于拋出異常。reject的參數會被catch捕獲。即便沒有調用reject,如果執(zhí)行過程中出錯了,也會被catch捕獲。
所以:
- 異步操作成功時,把結果告訴resolve回調函數 異步操作失敗 把錯誤告訴reject回調函數
- resolve 和 reject 回調函數 還有一個作用就是把 結果傳遞出去
Promise對象如何使用異步函數的執(zhí)行結果
通過then方法獲取異步操作的結果。
Promise實例有兩個方法:
- then 指定了resolve狀態(tài)和reject的回調函數
- catch 專門用來捕獲Promise對象產生的錯誤
then和catch方法返回的是一個新的Promise對象,因為Promise對象具有then和catch方法,所以可以一直.then和.catch下去
axios返回的也是Promise對象,這也是為什么axios可以那么鏈式操作了
then方法的作用就是干這個
promise.then(function(value) {
// success
}, function(error) {
// failure
});
then方法的參數是兩個回調函數,都接受Promise對象傳出的值作為參數:
- 第一個參數是resolve狀態(tài)的回調函數
- 第二個參數是reject的回調函數, 這個參數是可選的
通常這個參數也不寫,因為Promise實例還有一個方法叫catch是專門用來捕獲異常的。
catch
catch 是 .then(null, rejection)的別名,用于指定發(fā)生錯誤時的回調函數。
一旦catch前面的任何一個Promise發(fā)生異常,都會被catch捕獲,包括Promise函數創(chuàng)建的Promise,還有.then返回的Promise,甚至catch前面如果還有一個catch在這個catch拋出的異常也會被后一個catch捕獲。
也就是說:
Promise對象的錯誤具有冒泡性質,會一直向后傳遞,直到被捕獲為止,也即是說,錯誤總會被下一個catch語句捕獲。
所以,既然這個catch這么厲害,then函數中的第二個參數常常被省略了,然后被這個catch方法替代。
所以通常這么寫:
promise.then().catch()
promise.then().then().catch()
promise.then().then().catch().then().catch()
所以下面例子第二種寫法好些。
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
一般總是建議,Promise 對象后面要跟catch方法,這樣可以處理 Promise 內部發(fā)生的錯誤。catch方法返回的還是一個 Promise 對象,因此后面還可以接著調用then方法和catch方法。
Promise 還有兩個常用方法:
- Promise.all() :將多個Promise實例,包裝成一個新的Promise實例。內部所有的Promise的狀態(tài)都變成fulfilled,這個Promise狀態(tài)才會變成fulfilled,返回值是一個數組,但是只要有一個 rejected 這個Promise對象就會變成rejected 返回第一個被reject的實例的返回值
- Promise.race():跟all方法一樣,只是race就想是賽跑,誰先有結果,返回誰的結果。不會等到所有的Promise都執(zhí)行完。
現有對象轉為Promise對象
Promise.resolve方法就起到這個作用
Promise.resolve('foo')
// 等價于
nnew Promise(function (resolve) {
resolve('foo')
})
Promise.resolve方法的參數分成四種情況:
- 參數是一個Promise實例 :不做任何修改、原封不動地返回這個實例。
- 參數是一個thenable對象 :將這個對象轉換為Promise對象,然后立刻執(zhí)行thenable的then方法
- 參數不是具有then方法的對象,或根本就不是對象 :反回一個新的Promise對象,狀態(tài)為resolved。
- 不帶有任何參數: 直接返回一個resolved狀態(tài)的Promise對象
立即resolved的對象,是在本輪事件循環(huán)結束時,而不是在下一輪循環(huán)時間開始時
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
上面代碼中,setTimeout(fn,0),在下一輪循環(huán)事件開始執(zhí)行
Promise.resolve()在本輪事件循環(huán)結束時執(zhí)行
console.log('one')立刻執(zhí)行,
因此上面的打印順序是 one two three
直接返回一個狀態(tài)為rejected Promise對象
Promise.reject(reason)方法也會返回一個新的 Promise 實例,該實例的狀態(tài)為rejected。
var p = Promise.reject('出錯了');
//等價于
new Promise(function (resolve,reject) {
reject('出錯了');
})
Promise.reject()方法的參數,會原封不動地作為reject的理由,變成后續(xù)方法的參數。不會像Promise.resolve那樣,根據不同的情況包裝Promise
async和await
async函數是Generator函數的語法糖,將Generator的星號換成async 將yield換成await
Generator是一個狀態(tài)機,封裝了多個內部狀態(tài),執(zhí)行 Generator 函數會返回一個遍歷器對象,返回的遍歷器對象,可以使用next依次遍歷 Generator 函數內部的每一個狀態(tài)。Generator 函數是分段執(zhí)行的,yield表達式是暫停執(zhí)行的標記,而next方法可以恢復執(zhí)行。Generator 函數的執(zhí)行必須靠執(zhí)行器。
async函數比Generator函數更好用
- 自帶執(zhí)行器,執(zhí)行起來,跟調用普通函數一樣
- async和await 語義更清晰,async表示函數里有異步操作,await 表示緊跟在后面的表達式需要等待結果
- await后面啥都可以跟,可以是Promise 也可以是對象和原始類型的值(數值、字符串和布爾值,但這時等同于同步操作)
- async函數的 返回值是 Promise
正常情況下,await命令是個Promise對象,如果不是 會被轉成一個 立即 resolved的對象
async函數完全可以看作多個異步操作,包裝成的一個 Promise 對象(因為await 函數返回的是Promise對象),而await命令就是內部then命令的語法糖。
然而,然而,我們沒寫錯誤處理。
async function f() {
return 'hello world';
}
f().then().catch()
正常情況下 async 函數中return結果會使Promise對象變?yōu)?resolved狀態(tài),返回值作為then方法回調函數的參數,而出錯則會使Promise對象的變?yōu)閞eject狀態(tài),錯誤會被catch捕獲。
因為 async函數 相當于對 多個Promise的封裝,所以必須等到內部所有的await命令執(zhí)行完,才會改變自己的狀態(tài)為resolved,除非 碰到return語句或者拋出了異常。
也就是說,正常情況下 只有async函數內部的異步操作執(zhí)行完,才會執(zhí)行then后面的語句。
只要一個await后面的Promise變?yōu)閞ejected,整個async函數就會中斷執(zhí)行,整個async返回的Promise對象就會是rejected狀態(tài)
async function f() {
await Promise.reject('出錯了');
await Promise.resolve('hello world'); // 不會執(zhí)行
}
因為第一個await后面的對象reject了,所以整個async函數就中斷執(zhí)行了
有時,我們希望即使前一個異步操作失敗,也不要中斷后面的異步操作。
這時可以將第一個await放在try...catch結構里面,這樣不管這個異步操作是否成功,第二個await都會執(zhí)行。
await命令后面的Promise對象,運行結果可能是rejected,所以最好把await命令放在try...catch代碼塊中。
try catch
try catch是JavaScript的異常處理機制,把可能出錯的代碼放在try語句塊中,如果出錯了,就會被catch捕獲來處理異常。如果不catch 一旦出錯就會造成程序崩潰。
如果有多個await命令,可以將其都放在try catch結構中,如果執(zhí)行出錯,catch會去捕獲異常
async function f() {
try {
await Promise.reject('出錯了');
console.log('上面已經出錯了');
return await Promise.resolve('hello world');
} catch(e) {
console.log(e);
}
}
f()
.then(v => console.log(v))
catch會去捕獲try代碼塊中的錯誤,只要有一個拋出了異常,就不會繼續(xù)執(zhí)行,所以上面的代碼不會打印上面已經出錯了也不會執(zhí)行return await Promise.resolve('hello world');
因為使用了trycatch 所以 async 是順利執(zhí)行完成的,其中的報錯 被 try catch處理了,所以異常不會被async返回的Promise的catch捕獲,因此async返回的Promise對象狀態(tài)是resolved。
如果異步函數沒有依賴關系,最好并發(fā)執(zhí)行
await 會等待后面的異步操作執(zhí)行完畢,才會繼續(xù)執(zhí)行
let foo = await getFoo();
let bar = await getBar();
上面的代碼會順序執(zhí)行,
如果需要多個await沒有相互依賴,最好讓他們同時觸發(fā),可以使用以下兩種方式:
- 使用Promise.all() 包裝一個新的Promise對象
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
- 不等待分別執(zhí)行,返回新的Promise對象
//沒用await 立即執(zhí)行返回 Promise對象
let fooPromise = getFoo();
let barPromise = getBar();
// 等待 Promise對象的結果 之前也說過 await就像是then的語法糖
let foo = await fooPromise;
let bar = await barPromise;