Promise和async/await
1、promise對(duì)象
promise 對(duì)象有三種狀態(tài):pending(進(jìn)行中)、fulfilled(已成功)和 rejected(已失敗)。promise 對(duì)象的狀態(tài)改變,只有兩種可能:從 pending 變?yōu)?fulfilled 和從 pending 變?yōu)?rejected。
new Promise((resolve, reject) => { if() { resolve(); }else { reject(); } });
const promise = new Promise(function(resolve, reject) { if (){ resolve(value); } else { reject(error); } });
promise 構(gòu)造函數(shù)接受一個(gè)函數(shù)作為參數(shù),該函數(shù)的兩個(gè)參數(shù)分別是 resolve 和 reject,它們是兩個(gè)函數(shù),由 JavaScript 引擎提供,不用自己部署。
resolve 函數(shù)將 promise 對(duì)象的狀態(tài)從“未完成”變?yōu)椤俺晒Α保磸?pending 變?yōu)?resolved)可以將數(shù)據(jù)作為參數(shù)傳遞出去。reject 函數(shù)的作用是,將 promise 對(duì)象的狀態(tài)從“未完成”變?yōu)椤笆 保磸?pending 變?yōu)?rejected),也可以將某些錯(cuò)誤信息作為參數(shù)傳遞出去。
由于Promise 新建后會(huì)立即執(zhí)行,所以可以在 promise 外面再包裹一層函數(shù):
function timeout(ms) { return new Promise((resolve, reject) => { setTimeout(resolve, ms, 'done'); }); } timeout(100).then((value) => { console.log(value); });
請(qǐng)注意:在 promise 構(gòu)造函數(shù)中如果不 resolve 或者 reject 改變 promise 對(duì)象的狀態(tài)的話,后面的 then 是不會(huì)執(zhí)行的。但是如果在 then 中不改變狀態(tài),后面的鏈?zhǔn)?then 仍會(huì)執(zhí)行。
//下面的then里面的程序不會(huì)執(zhí)行 new Promise( (resolve, reject) => { console.log('promise'); }).then( ()=>{ console.log('resolve'); }, ()=>{ console.log('reject'); }) //下面的第一個(gè)和第二then里面的程序都會(huì)執(zhí)行 new Promise( (resolve, reject) => { console.log('promise'); resolve(); }).then( ()=>{ console.log('then1'); }).then( ()=>{ console.log('then2'); })
1.1、then() 方法
Promise 實(shí)例是一個(gè)對(duì)象,不是一個(gè)函數(shù)。promise 實(shí)例生成以后,可以用 then 方法分別指定 resolved 狀態(tài)和 rejected 狀態(tài)的回調(diào)函數(shù)。
new Promise().then(() => {}, () => {});
let promise = new Promise(function(resolve, reject) { if (){ resolve(value); } else { reject(error); } }); promise.then(function(value) { console.log(value) }, function(error) { console.log(error) });
then 方法可以接受兩個(gè)回調(diào)函數(shù)作為參數(shù)。第一個(gè)回調(diào)函數(shù)是 promise 對(duì)象的狀態(tài)變?yōu)?resolved 時(shí)調(diào)用,第二個(gè)回調(diào)函數(shù)是 promise 對(duì)象的狀態(tài)變?yōu)?rejected 時(shí)調(diào)用,第二個(gè)函數(shù)是可選的,不一定要提供。這兩個(gè)函數(shù)都接受 promise 對(duì)象傳出的值作為參數(shù)。
1.2、then() 方法的鏈?zhǔn)綄懛?/h3>
then 方法返回的是一個(gè)新的 promise 實(shí)例,因此可以采用鏈?zhǔn)綄懛ǎ?then 方法后面再調(diào)用另一個(gè) then 方法。
1.2.1、then 方法里面返回一個(gè)確定值時(shí)
在一個(gè) then() 方法里面你可以 return 一個(gè)確定的“值”,此時(shí) then 會(huì)將這個(gè)確切的值傳入一個(gè)默認(rèn)的新的 Promise 實(shí)例,并且這個(gè) Promise 實(shí)例會(huì)立即置為 fulfilled 狀態(tài),將 return 的值作為 resolve 方法的參數(shù)傳遞出去,以供接下來的 then 方法里使用。
let p1 = new Promise((resolve,reject) => { resolve('aaa') }) p1.then((data) => { data = data + 'bbb' return data // 此時(shí)data會(huì)作為resolve的參數(shù)傳遞出去 }).then((val) => { console.log(val + ' sucess'); },(err) => { console.log(err + ' error'); }) //輸出: aaabbb sucess
1.2.1、then 方法里面返回一個(gè) promise 實(shí)例
如果 then 方法里面返回的還是一個(gè) promise 對(duì)象,這時(shí)后一個(gè)回調(diào)函數(shù),就會(huì)等待該 promise 對(duì)象的狀態(tài)發(fā)生變化,才會(huì)被調(diào)用。
//第一個(gè)異步任務(wù) function a(){ return new Promise(function(resolve, reject){ resolve("a函數(shù)"); }); } //第二個(gè)異步任務(wù) function b(data_a){ return new Promise(function(resolve, reject){ console.log(data_a); resolve("b函數(shù)"); }); } //連續(xù)調(diào)用 a().then(function(data){ return b(data); // 此時(shí)then方法里面返回的是一個(gè)promise對(duì)象,后面的then會(huì)等待該promise對(duì)象的狀態(tài)發(fā)生改變才會(huì)被調(diào)用 }).then((data) => { console.log(data + 'sucess') }, (err) => { console.log(err + 'rejected') }) //輸出:a函數(shù) b函數(shù)sucess
上面的最后一個(gè) then 函數(shù)等待前面的 then 函數(shù)里面的 promise 對(duì)象狀態(tài)發(fā)生改變,如果變?yōu)?resolved ,就調(diào)用第一個(gè)回調(diào)函數(shù),如果狀態(tài)變?yōu)?rejected,就調(diào)用第二個(gè)回調(diào)函數(shù)。
1.2.1、then 方法里面不返回
如果 then 方法不返回?cái)?shù)據(jù),那么后面的 then 將無法獲取到前面的數(shù)據(jù),但是后面的 then 方法仍能執(zhí)行。
//第一個(gè)異步任務(wù) function a(){ return new Promise(function(resolve, reject){ resolve("a函數(shù)"); }); } //第二個(gè)異步任務(wù) function b(data_a){ return new Promise(function(resolve, reject){ console.log(data_a); resolve("b函數(shù)"); }); } a().then(function(data){ console.log(data) //不返回 }).then((data) => { console.log(data + ' sucess') }, (err) => { console.log(err + ' rejected') }) //輸出: a函數(shù) undefined sucess
1.3、catch() 方法
Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的別名,用于指定發(fā)生錯(cuò)誤時(shí)的回調(diào)函數(shù)。
p.then((val) => console.log(val)) .then(null, (err) => console.log(err)); // 相當(dāng)于 p.then((val) => console.log(val)) .catch((err) => console.log(err));
catch 方法中斷調(diào)用鏈:
在很多情況下,如果連續(xù)的幾個(gè)異步任務(wù),其中某個(gè)異步任務(wù)處理失敗,那么接下來的幾個(gè)任務(wù)很大程度上就不需要繼續(xù)處理了,我們可以使用 catch 方法來終止then的調(diào)用鏈。
function a() { return new Promise(function (resolve, reject) { setTimeout(function () { reject("error"); }, 1000); }); } //這樣做不會(huì)中斷 //下面輸出:222 333 a().then(function (data) { console.log(111); return data }, function (data) { //如果是這樣處理rejected狀態(tài),并不會(huì)中斷調(diào)用鏈 console.log(222); return data; }).then(function (data) { console.log(333); }) //在調(diào)用鏈的末尾加上catch方法,當(dāng)某個(gè)環(huán)節(jié)的Promise的異步處理出錯(cuò)時(shí),將中斷其后的調(diào)用,直接跳到最后的catch //下面直接輸出: 'error' a().then(function (data) { console.log(111); return data }).then(function (data) { console.log(222); }).catch(function (e) { //rejected的狀態(tài)將直接跳到catch里,剩下的調(diào)用不會(huì)再繼續(xù) console.log(e); });
使用 catch 方法時(shí),如果前面的函數(shù)里面有 reject 或者函數(shù)里面有錯(cuò)誤的話,就會(huì)被 catch 方法捕獲,立即跳轉(zhuǎn)到 catch 方法里執(zhí)行。前面的回調(diào)函數(shù)中,不管是運(yùn)行中有錯(cuò)誤,或者是執(zhí)行了 reject ,都會(huì)立即被 catch 方法捕獲。
使用 catch 捕獲了錯(cuò)誤,catch 后面的代碼能繼續(xù)正常執(zhí)行。
new Promise().then().catch(() => { console.log('catch 錯(cuò)誤') }); console.log(111); //這里的 111 仍能正常輸出
2、async/await 方法
async 方法里面有 await 命令,await 命令后面跟著異步操作(可以是請(qǐng)求接口或者其他異步操作等),這時(shí) async 函數(shù)會(huì)停下來,等待 await 命令后面的異步操作執(zhí)行完畢,將結(jié)果返回,然后再繼續(xù)執(zhí)行下去。如果后面再遇到 await 命令仍是如此,所以,async 函數(shù)里面可以看做是同步操作,語句都是一行一行地依次執(zhí)行的,語句執(zhí)行順序非常清晰。
2.1、async 函數(shù)的定義形式
// 函數(shù)聲明 async function foo() {} const foo = async function () {}; // 對(duì)象的方法 let obj = { async foo() {} }; // 箭頭函數(shù) const foo = async () => {}; // Class 的方法 class Storage { constructor() { this.cachePromise = caches.open('avatars'); } async getAvatar(name) { const cache = await this.cachePromise; return cache.match(`/avatars/${name}.jpg`); } }
2.2、async 函數(shù)里面的 await 命令
注意,await 命令后面只能是 Promise 對(duì)象或者原始類型的值(數(shù)值、字符串和布爾值,但這時(shí)會(huì)自動(dòng)轉(zhuǎn)成立即 resolved 的 Promise 對(duì)象)。只有后面的 promise 對(duì)象返回了值 await 命令才算執(zhí)行完畢,程序才會(huì)繼續(xù)執(zhí)行。
2.2.1、await 跟著 promise 對(duì)象
一般來說,await命令后面應(yīng)該跟著一個(gè) Promise 對(duì)象,如果該 promise 對(duì)象觸發(fā)的是 resolve 方法,那么 await 就會(huì)接收到 resolve 方法的參數(shù),并將其返回。
function timeout() { return new Promise((resolve) => { setTimeout(function (){ resolve('aaa') },1000); }); } async function asyncPrint() { console.log(await this.timeout()); //await會(huì)返回resolve的參數(shù) console.log(111); } asyncPrint(); //一秒鐘過后按順序執(zhí)行,依次輸出 aaa 111
如果觸發(fā)的是 reject 方法,await 無法接收到 reject 返回的值,此時(shí)必須用 catch 方法來捕獲這個(gè)錯(cuò)誤,否則會(huì)報(bào)錯(cuò)。
(1)可以用 try...catch 來捕獲異常
注意,在 try...catch 里面捕獲異步操作的異常,必須使用 await 同步寫法,否則會(huì)捕獲不到。
async created () { async function f() { await Promise.reject("出錯(cuò)了!!"); } try { await f(); //注意,這里必須使用同步(await )的寫法,否則捕獲不到錯(cuò)誤 } catch (error) { console.log(error); //輸出 出錯(cuò)了!! } }
(2)也可以在調(diào)用的時(shí)候直接在后面加上 catch 來捕獲異常。
下面代碼中,await語句前面沒有return,但是reject方法的參數(shù)依然傳入了catch方法的回調(diào)函數(shù)。這里如果在await前面加上return,效果是一樣的。
async function f() { await Promise.reject('出錯(cuò)了!!'); } f().then(v => console.log(v)).catch(e => console.log(e)) //輸出: 出錯(cuò)了!!
function timeout() { return new Promise((resolve, reject) => { setTimeout(function() { reject("aaa"); }, 1000); }); } timeout().then(val => { console.log(val); }).catch(err => { console.log(err); //捕獲到異常 輸出 aaa })
reject 的參數(shù)會(huì)被后面第一個(gè)出現(xiàn)的 catch 方法捕獲到。
任何一個(gè)await語句后面的 Promise 對(duì)象變?yōu)?code>reject狀態(tài),那么整個(gè)async函數(shù)都會(huì)中斷執(zhí)行。
async function f() { await Promise.reject('出錯(cuò)了'); await Promise.resolve('hello world'); // 不會(huì)執(zhí)行 }
有時(shí),我們希望即使前一個(gè)異步操作失敗,也不要中斷后面的異步操作。這時(shí)可以將第一個(gè)await放在try...catch結(jié)構(gòu)里面,這樣不管這個(gè)異步操作是否成功,第二個(gè)await都會(huì)執(zhí)行。
async function f() { try { await Promise.reject('出錯(cuò)了'); //使用await寫法 } catch(e) { console.log(e); } return await Promise.resolve('helloworld'); } f().then(v => console.log(v)) //輸出: helloworld
2.2.2、await 跟著原始類型的值
await 命令后面只能是 Promise 對(duì)象或者原始類型的值,如果 await 命令后面是原始類型的值,則直接返回對(duì)應(yīng)的值。
async function f() { // 等同于 return 123; let a = await 123; console.log(a) } f(); //輸出: 123
2.2.3、await 后面跟其他類型的值將沒有效果
function timeout() { setTimeout(() => { console.log('aaa'); }, 1000); } async function asyncPrint() { await timeout(); //此時(shí)的await不起作用 console.log(111); } asyncPrint(); //先輸出111,后輸出aaa,并不能起到順序執(zhí)行的作用
2.3、async 函數(shù)返回的值是一個(gè) promise 對(duì)象
async函數(shù)返回的是一個(gè) Promise 對(duì)象,可以使用then方法添加回調(diào)函數(shù)。
async函數(shù)內(nèi)部return語句返回的值,會(huì)成為then方法回調(diào)函數(shù)的參數(shù)。
async function f() { return 'hello world'; } f().then(v => console.log(v)) // "hello world"
async函數(shù)返回的 Promise 對(duì)象,必須等到內(nèi)部所有await命令后面的 Promise 對(duì)象執(zhí)行完,才會(huì)發(fā)生狀態(tài)改變,除非遇到return語句或者拋出錯(cuò)誤。也就是說,只有等到async函數(shù)內(nèi)部的await 后面的異步操作執(zhí)行全部完,promise 對(duì)象狀態(tài)才會(huì)發(fā)生改變,然后才能執(zhí)行后面添加的then方法指定的回調(diào)函數(shù)。
let p1 = new Promise((resolve,reject)=>{ resolve('aaa') }) let p2 = new Promise((resolve,reject)=>{ resolve('aaa') }) async function getUrl(url) { let response = await p1; let html = await p2; return url; } getUrl('http://www.baidu.com').then((val) => { console.log(val) }) //只有等到 p1 和 p2 都執(zhí)行完畢后才會(huì)調(diào)用then里面的回調(diào)函數(shù)輸出值,輸出:http://www.baidu.com
2.4、async函數(shù)的鏈?zhǔn)接梅?/h3>
async函數(shù)返回的值是一個(gè) promise 對(duì)象,這意味著我們?cè)趫?zhí)行完一個(gè) async 函數(shù)完后,可以在后面接著寫 then 等操作。
2.4.1、async 函數(shù)返回原始類型值的情況
async 函數(shù)如果最后是返回原始類型的值,那么該原始類型值會(huì)被當(dāng)做被 resolve 的值,我們可以在該函數(shù)后面加 then 來繼續(xù)執(zhí)行操作,獲取返回的值。
async function main () { //返回原始類型值會(huì)被包裝為一個(gè)立即resolve的Promise對(duì)象 return 123; //相當(dāng)于 return Promise.resolve(123),其實(shí)也是一個(gè)promise對(duì)象 } //此時(shí)的鏈?zhǔn)綄懛?/span> const data = main() //此時(shí)data是一個(gè)promise對(duì)象,可以加then來繼續(xù)執(zhí)行操作 data.then(num => { console.log(num); //輸出123 })
2.4.2、async 函數(shù)返回promise對(duì)象的情況
其實(shí) async 函數(shù)返回原始類型值也相當(dāng)于是返回一個(gè) promise 對(duì)象,此時(shí)我們可以在另一個(gè) async 函數(shù)里用 await 接收 resolve 出來的值,也可以直接加 then 繼續(xù)操作。
async function main () { return new Promise((resolve, reject) => { resolve(123) }) } //此時(shí)的鏈?zhǔn)綄懛?/span> //可以在另一個(gè) async 函數(shù)里面接收它的結(jié)果 async function main2 () { const data = await main(); //await后面是一個(gè)promise對(duì)象,會(huì)等到該promise對(duì)象將值resolve出來 console.log(data); //輸出12,此時(shí)的data已經(jīng)獲取到了resolve出來的值。 } main2()
請(qǐng)注意:async 函數(shù)里,在 await 后面返回的值并不是 async 函數(shù)返回的值。
//下面的async函數(shù)相當(dāng)于沒有返回值,其他函數(shù)就接收不到 async function main () { new Promise((resolve, reject) => { return 123; }) } async function main2 () { const data = await main(); console.log(data); //輸出 undefined,因?yàn)閙ain函數(shù)根本沒有返回值。 } main2()
2.5、async 函數(shù)的錯(cuò)誤處理
如果await后面的異步操作出錯(cuò),或者是await 后面的 promise 對(duì)象被 reject,那么async函數(shù)后面的語句將不會(huì)執(zhí)行,async 函數(shù)會(huì)立即返回一個(gè) rejected 狀態(tài)的promise對(duì)象。
async function f() { await new Promise(function (resolve, reject) { throw new Error('出錯(cuò)了'); }); } f().then(v => console.log(v)).catch(e => console.log(e)) // Error:出錯(cuò)了 async function f() { await Promise.reject('出錯(cuò)了??!'); } f().then(v => console.log(v)).catch(e => console.log(e)) //輸出: 出錯(cuò)了!!
防止出錯(cuò)的方法,也是將其放在try...catch代碼塊之中。
async function f() { try { await new Promise(function (resolve, reject) { throw new Error('出錯(cuò)了'); }); }catch (e) {} return await ('hello world'); //這里仍然能執(zhí)行 } f().then((data) => {console.log(data);}); //輸出: hello world
2.6、await只會(huì)阻塞本函數(shù)體內(nèi)的語句
需要注意,await函數(shù)只會(huì)阻塞本函數(shù)體內(nèi)的語句,不是本函數(shù)體內(nèi)的語句并不會(huì)導(dǎo)致堵塞。
比如下面的代碼,將會(huì)輸出222 aaa 111,也就是說await只會(huì)堵塞本身函數(shù)體 asyncPrint 內(nèi)的語句,輸出222的語句并不會(huì)堵塞。所以我們大可以用 await 來進(jìn)行異步操作。
function timeout() { return new Promise((resolve) => { setTimeout(function (){ resolve('aaa') },1000); }); } async function asyncPrint() { console.log(await this.timeout()); //await會(huì)返回resolve的參數(shù) console.log(111); } asyncPrint(); //一秒鐘過后按順序執(zhí)行,依次輸出 aaa 111 console.log(222)
下面代碼將輸出:222 333 aaa 第1個(gè) bbb 第2個(gè) ccc 第3個(gè)
function timeout() { return new Promise((resolve) => { setTimeout(function () { resolve('aaa') }, 3000); }); } function timeout2() { console.log(222); return new Promise((resolve) => { setTimeout(function () { resolve('bbb') }, 3000); }); } function timeout3() { console.log(333); return new Promise((resolve) => { setTimeout(function () { resolve('ccc') }, 3000); }); } async function asyncPrint() { console.log(await this.timeout()); //await會(huì)返回resolve的參數(shù) console.log('第1個(gè)'); } async function asyncPrint2() { console.log(await this.timeout2()); //await會(huì)返回resolve的參數(shù) console.log('第2個(gè)'); } async function asyncPrint3() { console.log(await this.timeout3()); //await會(huì)返回resolve的參數(shù) console.log('第3個(gè)'); } asyncPrint(); asyncPrint2(); asyncPrint3();

浙公網(wǎng)安備 33010602011771號(hào)