解決異步編程的方法—promise與await
promise是什么蚕甥?
Promise择懂,簡(jiǎn)單說(shuō)就是一個(gè)容器哑梳,里面保存著某個(gè)未來(lái)才會(huì)結(jié)束的事件(通常是一個(gè)異步操作)的結(jié)果。從語(yǔ)法上說(shuō)袁串,Promise 是一個(gè)對(duì)象,從它可以獲取異步操作的消息呼巷。Promise 提供統(tǒng)一的 API囱修,各種異步操作都可以用同樣的方法進(jìn)行處理。簡(jiǎn)單來(lái)說(shuō)王悍,promise的作用就是將異步操作以同步操作的流程表達(dá)出來(lái)破镰,避免了層層嵌套的回調(diào)函數(shù)。
promise的特點(diǎn)
① 對(duì)象的狀態(tài)不受外界影響:promise異步操作有三種狀態(tài):進(jìn)行中压储,已成功鲜漩,已失敗。只有異步操作才能改變這個(gè)狀態(tài)集惋。
②一變則不變:promise狀態(tài)一旦改變孕似,就不會(huì)再發(fā)生變化,promise對(duì)象改變的兩種可能刮刑,進(jìn)行中—>已成功喉祭,進(jìn)行中—>已失敗。
promise的基本用法
promise對(duì)象是一個(gè)構(gòu)造函數(shù)雷绢,用來(lái)生成promise實(shí)例
例子:
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 異步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
其中接受的參數(shù)是resolve和reject兩個(gè)函數(shù)
resolve的作用:將promise對(duì)象的狀態(tài)由進(jìn)行中—>已完成泛烙。并將異步操作的結(jié)果作為參數(shù)傳遞出去
rejected的作用:將promise對(duì)象的狀態(tài)由進(jìn)行中—>已失敗,并將異步失敗的原因作為參數(shù)傳遞出去习寸。
注意:調(diào)用resolve或reject并不會(huì)終結(jié)promise的參數(shù)函數(shù)的執(zhí)行
例子:
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1
上面代碼調(diào)用了resolve(1)以后胶惰,后面的console(2)還是會(huì)執(zhí)行,并且會(huì)首先打印出來(lái)霞溪。這是因?yàn)榱⒓磖esolved的promise是在本輪事件循環(huán)的末尾執(zhí)行孵滞,總是晚于本輪循環(huán)的同步任務(wù)中捆。
then的用法
promise實(shí)例生成后,用then方法分別指定resolved狀態(tài)和rejucted狀態(tài)的回調(diào)函數(shù)坊饶。
例子:
promise.then(function(value) {
// success
}, function(error) {
// failure
});
then方法可以接受兩個(gè)回調(diào)函數(shù)作為參數(shù)泄伪,第一個(gè)回調(diào)函數(shù)是當(dāng)promise對(duì)象狀態(tài)是resolve(已完成)的時(shí)候調(diào)用,第二個(gè)回調(diào)函數(shù)(可選)是當(dāng)promise對(duì)象狀態(tài)是reject(已失斈浼丁)的時(shí)候調(diào)用蟋滴。
如例子:
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
結(jié)果是done
鏈?zhǔn)降膖hen用法
then方法返回的是一個(gè)新的Promise實(shí)例(注意,不是原來(lái)那個(gè)Promise實(shí)例)痘绎。因此可以采用鏈?zhǔn)綄懛ń蚝磘hen方法后面再調(diào)用另一個(gè)then方法
例子
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function funcA(comments) {
console.log("resolved: ", comments);
}, function funcB(err){
console.log("rejected: ", err);
});
上面代碼中,第一個(gè)then方法指定的回調(diào)函數(shù)孤页,返回的是另一個(gè)Promise對(duì)象尔苦。這時(shí),第二個(gè)then方法指定的回調(diào)函數(shù)行施,就會(huì)等待這個(gè)新的Promise對(duì)象狀態(tài)發(fā)生變化允坚。如果變?yōu)閞esolved,就調(diào)用funcA蛾号,如果狀態(tài)變?yōu)閞ejected稠项,就調(diào)用funcB。
catch方法
promise對(duì)象中鲜结,如果異步操作拋出錯(cuò)誤展运,狀態(tài)就會(huì)變?yōu)閞ejected,就會(huì)調(diào)用catch方法指定的回調(diào)函數(shù)處理這個(gè)錯(cuò)誤精刷,另外乐疆,then方法指定的回調(diào)函數(shù),如果運(yùn)行中拋出錯(cuò)誤也會(huì)被catch方法捕獲贬养。
例子:
p.then((val) => console.log('fulfilled:', val))
.catch((err) => console.log('rejected', err));
// 等同于
p.then((val) => console.log('fulfilled:', val))
.then(null, (err) => console.log("rejected:", err));
promise對(duì)象的錯(cuò)誤具有“冒泡”性質(zhì),會(huì)一直向后傳琴庵,直到被捕獲误算,也就是說(shuō),會(huì)跳過(guò)中間的then函數(shù)
例子:
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 處理前面三個(gè)Promise產(chǎn)生的錯(cuò)誤
});
finally方法
finally方法用于指定不管promise對(duì)象最后狀態(tài)如何迷殿,都會(huì)執(zhí)行的操作儿礼。
例子:
server.listen(port)
.then(function () {
// ...
})
.finally(server.stop);
例子是服務(wù)器使用promise處理請(qǐng)求,然后使用finally()方法關(guān)掉服務(wù)器庆寺。
promise.all()方法
promise.all方法用于將多個(gè)promise實(shí)例蚊夫,包裝成一個(gè)新的promise實(shí)例。
比如:const p = Promise.all([p1, p2, p3]);
Promise.all方法懦尝,接受的是一個(gè)數(shù)組作為參數(shù)知纷,其中的元素都是promise實(shí)例壤圃,如果不是,則會(huì)自動(dòng)將參數(shù)轉(zhuǎn)變?yōu)閜romie實(shí)例琅轧。
p的狀態(tài)是有它的數(shù)組里面的元素決定的伍绳,分兩種狀態(tài)(用上面舉例)
①只有p1 p2 p3的狀態(tài)都變成fulfilled(已完成)的狀態(tài)才會(huì)變成fulfilled(已完成),此時(shí)p1 p2 p3的返回值組成一個(gè)數(shù)組乍桂,傳遞給p的回調(diào)函數(shù)冲杀。
②只有p1 p2 p3之中,有一個(gè)被rejucted(未完成)睹酌,p的狀態(tài)就會(huì)變成rejected(未完成)权谁,此時(shí)第一個(gè)被reject的實(shí)例的返回值,會(huì)傳遞給p的回調(diào)函數(shù)憋沿。
例子:
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result);
const p2 = new Promise((resolve, reject) => {
throw new Error('報(bào)錯(cuò)了');
})
.then(result => result);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// Error: 報(bào)錯(cuò)了enter code here
代碼中旺芽,因?yàn)閜2報(bào)錯(cuò)了,所會(huì)執(zhí)行promise,all的catch方法
promise.race()方法
Promise.race方法同樣是將多個(gè) Promise 實(shí)例卤妒,包裝成一個(gè)新的 Promise 實(shí)例
const p = Promise.race([p1, p2, p3]);
如果p1 p2 p3不是promise實(shí)例甥绿,也會(huì)自動(dòng)轉(zhuǎn)變成promise實(shí)例
與promise.all不同的是,上面代碼中则披,只用p1共缕、p2、p3之中有一個(gè)實(shí)例率先改變狀態(tài)士复,p的狀態(tài)就跟著改變图谷。那個(gè)率先改變的 Promise 實(shí)例的返回值,就傳遞給p的回調(diào)函數(shù)阱洪。
async函數(shù)是什么
async的引入便贵,使得異步操作變得更加方便,那async函數(shù)是什么冗荸,其實(shí)它是Generator函數(shù)的語(yǔ)法糖承璃。使異步函數(shù)、回調(diào)函數(shù)在語(yǔ)法上看上去更像同步函數(shù)蚌本。Generator這里就不介紹了盔粹。我們直接來(lái)學(xué)習(xí)async
async的基本用法
async返回值是一個(gè)promise對(duì)象,因此可以使用then方法添加回調(diào)函數(shù)程癌,當(dāng)函數(shù)執(zhí)行的時(shí)候舷嗡,一旦遇到await就會(huì)先返回,等到異步操作完成嵌莉,再接著執(zhí)行函數(shù)體內(nèi)后面的內(nèi)容进萄。
例子:
async function getStockPriceByName(name) {
const symbol = await getStockSymbol(name);
const stockPrice = await getStockPrice(symbol);
return stockPrice;
}
getStockPriceByName('goog').then(function (result) {
console.log(result);
});
async函數(shù)的使用形式
//函數(shù)聲明
async function foo(){}
//函數(shù)表達(dá)式
const foo = async function(){};
//對(duì)象的方法
let obj = {async foo(){}};
obj.foo().then(...)
//class的方法
class Storage{
consttuctor(){
this.cachePromise=caches.open('avatars');
}
async getAvatar(name){
const cache = await this.cachePromise;
return cache.match('/avatars/${name}.jpg')};
}
}
const storage =new Storage();
storage.getAvatar('jake').then(....);
}
}
const storage =new Storage();
storage.getAvatar('jake').then(...)
//箭頭函數(shù)
const foo =async()=>{};
async 函數(shù)內(nèi)部return語(yǔ)句返回的值中鼠,會(huì)成為then方法調(diào)用函數(shù)的參數(shù)可婶。
例子:
async function f(){
return ‘hello world’;
}
f().then(v=>console.log(v))
//"hello world"
async promise 對(duì)象的狀態(tài)變化
只有等到async函數(shù)內(nèi)部的一部操作執(zhí)行完兜蠕,才會(huì)執(zhí)行then方法指定的回調(diào)函數(shù)扰肌。
例子:
async function getTitle(url) {
let response = await fetch(url);
let html = await response.text();
return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"
await 命令
正常情況下,await命令后面跟著的是一個(gè)promise對(duì)象熊杨,如果不是會(huì)自動(dòng)轉(zhuǎn)化為promise對(duì)象
例子:
async function f(){
return await 123;
}
f().then(v =>console.log(v))
//123
當(dāng)一個(gè)await語(yǔ)句后面的promise變?yōu)閞eject曙旭,那么整個(gè)函數(shù)都會(huì)中斷執(zhí)行。
例子:
async function f() {
await Promise.reject('出錯(cuò)了');
await Promise.resolve('hello world'); // 不會(huì)執(zhí)行
}
錯(cuò)誤處理
如果await 后面的異步操作有錯(cuò)晶府,那么等同于async函數(shù)返回的promis對(duì)象被reject (上文講promise對(duì)象的時(shí)候有提到過(guò)桂躏,冒泡性質(zhì))
例子:
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ò)了
使用try ....catch代碼塊課防止出錯(cuò)。
例子:
async function f() {
try {
await new Promise(function (resolve, reject) {
throw new Error('出錯(cuò)了');
});
} catch(e) {
}
return await('hello world');
}
也可以將多個(gè)await命令都放在try..catch結(jié)構(gòu)中
async function main() {
try {
const val1 = await firstStep();
const val2 = await secondStep(val1);
const val3 = await thirdStep(val1, val2);
console.log('Final: ', val3);
}
catch (err) {
console.error(err);
}
}
注意點(diǎn)
①await命令只能用在async函數(shù)中川陆,用在普通函數(shù)中會(huì)報(bào)錯(cuò)剂习。
詳細(xì)可以看阮一峰