閱讀本篇文章之前一定要明白異步解決方案和 http 的關(guān)系。異步是異步囚聚,http 是http跨晴。http 請(qǐng)求是一個(gè)異步的過(guò)程,而異步并不一定就和 http 請(qǐng)求聯(lián)系在一起根吁。所以不要提起異步就默認(rèn)為就是 ajax請(qǐng)求數(shù)據(jù)员淫。
Promise
Promise 對(duì)象用于表示一個(gè)異步操作的最終狀態(tài)(玩成或失敗)击敌,以及其返回的值介返。
看下控制臺(tái)輸出的 Promise 對(duì)象信息:
Promise 的構(gòu)造函數(shù)接受一個(gè)函數(shù)作為參數(shù)。傳入的函數(shù)參數(shù)可以有兩個(gè): resolve 和 reject沃斤。resolve 是將 Promise 從 pending 狀態(tài)置為 fulfilled 狀態(tài)圣蝎。 reject 是將 Promise 狀態(tài)從 pending 置為 rejected 狀態(tài)。我們可以簡(jiǎn)單的理解成:resolve 表示異步操作成功后的回調(diào)函數(shù)衡瓶, reject 表示異步操作失敗后的回調(diào)函數(shù)徘公。
Promise 最直接的好處就是鏈?zhǔn)秸{(diào)用。
沒(méi)有使用 Promise 來(lái)實(shí)現(xiàn)異步操作:
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
按上面的寫法哮针,做很多的多層回調(diào)會(huì)讓我們陷入經(jīng)典的回調(diào)地獄关面。
而使用 Promise 來(lái)實(shí)現(xiàn)異步多層回調(diào):
doSomething()
.then(function(result) {
return doSomethingElse(result);
})
.then(function(newResult) {
return doThirdThing(newResult);
})
.then(function(finalResult) {
console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);
對(duì)比于我們可能會(huì)陷入的回調(diào)地獄坦袍,Promise 簡(jiǎn)化了層層回調(diào)的寫法。而且缭裆,用維護(hù)狀態(tài)键闺、傳遞狀態(tài)的方式來(lái)使得回調(diào)函數(shù)能夠及時(shí)調(diào)用,這比傳遞 callback 函數(shù)要簡(jiǎn)單澈驼、靈活的多辛燥。
在 then 方法中,我們除了可以 return 一個(gè) Promise 對(duì)象缝其,還可以直接 return 數(shù)據(jù)挎塌,可以在之后的 then 中接收到 return 數(shù)據(jù)了:
function runAsync1(){
var p = new Promise(function(resolve, reject){
//做一些異步操作
setTimeout(function(){
console.log('異步任務(wù)1執(zhí)行完成');
resolve('隨便什么數(shù)據(jù)1');
}, 1000);
});
return p;
}
function runAsync2(){
var p = new Promise(function(resolve, reject){
//做一些異步操作
setTimeout(function(){
console.log('異步任務(wù)2執(zhí)行完成');
resolve('隨便什么數(shù)據(jù)2');
}, 2000);
});
return p;
}
runAsync1()
.then(function(data){
console.log(data + '@@');
return runAsync2();
})
.then(function(data){
console.log(data + '##');
return '直接返回?cái)?shù)據(jù)'; //這里直接返回?cái)?shù)據(jù)
})
.then(function(data){
console.log(data + '--');
});
上面的例子只是講了 Promise 的用法,其中涉及到的只有成功時(shí)回調(diào)的 resolve,那失敗狀態(tài)又是如何使用的呢内边?
function getNumber(){
var p = new Promise(function(resolve, reject){
//做一些異步操作
setTimeout(function(){
var num = Math.ceil(Math.random()*10); //生成1-10的隨機(jī)數(shù)
if(num<=5){
resolve(num);
}
else{
reject('數(shù)字太大了');
}
}, 2000);
});
return p;
}
getNumber()
.then(
function(data){
console.log('resolved');
console.log(data);
},
function(reason, data){
console.log('rejected');
console.log(reason);
}
);
我們?cè)?getNumber 函數(shù)中調(diào)用 Promise 中的 reject 來(lái)將某種失敗的狀態(tài)傳遞出來(lái)榴都,然后在 then 中傳遞了兩個(gè)參數(shù)。 then 方法可以接受兩個(gè)參數(shù)漠其,第一個(gè)對(duì)應(yīng) resolve 的回調(diào)嘴高,第二個(gè)對(duì)應(yīng) reject 的回調(diào)。所以在這兩個(gè)回調(diào)函數(shù)中和屎,我們可以獲取對(duì)應(yīng)的成功或失敗返回的數(shù)據(jù)拴驮。
我們常用的 Promise 對(duì)象除了 then 方法外,還有一個(gè) catch 方法柴信。該方法是用來(lái)干啥的呢套啤?then 里的參數(shù)是可選的, catch(failureCallback)
是 then(null, failureCallback)
的縮略形式随常。catch 的作用其實(shí)和 then 的第二個(gè)參數(shù)一樣潜沦,用來(lái)指定 reject 的回調(diào):
getNumber()
.then(function(data){
console.log('resolved');
console.log(data);
})
.catch(function(reason){
console.log('rejected');
console.log(reason);
});
catch 除了可以用來(lái)表示 reject 的回調(diào)外,它還有另外一個(gè)作用:在執(zhí)行 resolve 的回調(diào)(也就是上面代碼 then 中的第一個(gè)參數(shù))時(shí)绪氛,如果拋出異常了(代碼出錯(cuò)了)唆鸡,那么并不會(huì)報(bào)錯(cuò)卡死 Js, 而是會(huì)進(jìn)到這個(gè) catch 方法中。
getNumber()
.then(function(data){
console.log('resolved');
console.log(data);
console.log(somedata); //此處的somedata未定義
})
.catch(function(reason){
console.log('rejected');
console.log(reason);
});
Promise.all()
Promise 的 all 方法提供了并行執(zhí)行異步操作的能力枣察,并且在所有異步操作執(zhí)行完后才執(zhí)行回調(diào)喇闸。
Promise
.all([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
console.log(results);
});
用 Promise.all 來(lái)執(zhí)行,all 接收一個(gè)數(shù)組參數(shù)询件,里面的值最后都會(huì)返回 Promise 對(duì)象。這樣唆樊,三個(gè)異步操作并行執(zhí)行宛琅,等到它們都執(zhí)行完后才會(huì)進(jìn)到 then 里面。
Promise.race()
race 意為 “競(jìng)爭(zhēng)”逗旁。 all 方法的效果實(shí)際上是誰(shuí)跑的慢嘿辟,以誰(shuí)為準(zhǔn)執(zhí)行回調(diào)舆瘪,進(jìn)而達(dá)到一個(gè)并行的效果。race 的效果則和 all 的效果相反红伦,誰(shuí)跑的最快就先回調(diào)執(zhí)行誰(shuí)英古。
Promise
.race([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
console.log(results);
});
用 Promise.race 來(lái)執(zhí)行,race 接收一個(gè)數(shù)組參數(shù)昙读,數(shù)組里面的值都是可以最終執(zhí)行返回 Promise 對(duì)象召调。調(diào)用執(zhí)行的時(shí)候都是并行執(zhí)行的,一旦數(shù)組參數(shù)中的某個(gè)對(duì)象執(zhí)行完并返回 Promise 對(duì)象就會(huì)立即進(jìn)入 then 方法繼續(xù)下一步蛮浑。而后面數(shù)組其他的對(duì)象執(zhí)行完依次重復(fù)該過(guò)程唠叛,體現(xiàn)了一個(gè)競(jìng)速的效果。
Async/await
async/await
是 es7 提出的異步特性
async
字面意思是“異步”沮稚,用于聲明一個(gè)函數(shù)是異步的艺沼。await
的字面意思是“等待”,用來(lái)等待異步函數(shù)完成蕴掏。
通常來(lái)說(shuō)async/await
都是跟隨 Promise
一起使用的障般。因?yàn)?code>async返回的是一個(gè) Promise
對(duì)象。
/**
* 成功執(zhí)行
*/
async function funcSuccess() {
const count = 10;
return count;
}
funcSuccess().then(
(res) => {
console.log(res);
}
);
/**
* 失敗執(zhí)行
*/
async function funcFail() {
const count = 10 + i;
console.log('測(cè)試是否繼續(xù)執(zhí)行');
return count;
}
funcFail()
.then(
(res) => {
console.log('執(zhí)行成功', res);
}
)
.catch(
(error) => {
console.log('執(zhí)行失敗', error);
}
);
如果 async 函數(shù)執(zhí)行順利并結(jié)束盛杰,返回的 Promise 對(duì)象的狀態(tài)會(huì)從等待狀態(tài)轉(zhuǎn)變成功狀態(tài)挽荡,并輸出 return 命令返回的結(jié)果(沒(méi)有則為 undefined)。如果 async 函數(shù)執(zhí)行途中失敗饶唤,JS 會(huì)認(rèn)為 async 函數(shù)已經(jīng)完成執(zhí)行徐伐,返回的 Promise 對(duì)象的狀態(tài)會(huì)從等待轉(zhuǎn)變成失敗,并輸出錯(cuò)誤信息募狂。
await
只能用在async
函數(shù)里面办素,存在于 async 內(nèi)部的普通函數(shù)也不行。
/**
* await 只能用在 async 函數(shù)內(nèi)部祸穷,存在于 async 函數(shù)內(nèi)部的普通函數(shù)也不行
* 下面這段代碼會(huì)直接報(bào)錯(cuò)
*/
async function testA() {
function abc () {
const ab = await new Promise(resolve => {
setTimeout(() => {
resolve(10);
}, 2000);
});
}
}
引擎會(huì)統(tǒng)一將 await 后面的跟隨值視為一個(gè) Promise性穿, 對(duì)于不是 Promise 對(duì)象的值會(huì)調(diào)用 Promise.resolve() 進(jìn)行轉(zhuǎn)化。即便此值為一個(gè) Error 實(shí)例雷滚,經(jīng)過(guò)轉(zhuǎn)化后需曾,引擎依然視其為一個(gè)成功的 Promise, 其數(shù)據(jù)為 Error 的實(shí)例祈远。
當(dāng)函數(shù)執(zhí)行到 await 命令時(shí)呆万,會(huì)暫停執(zhí)行并等待其后的 Promise 結(jié)束。如果該 Promise 對(duì)象最終成功车份,則會(huì)返回成功的返回值谋减,相當(dāng)于將 await xxx 替換成 返回值。如果該 Promise 對(duì)象最終失敗扫沼,且錯(cuò)誤沒(méi)有被捕獲出爹,引擎會(huì)直接停止執(zhí)行 async 函數(shù)并將其返回對(duì)象的狀態(tài)更改為失敗庄吼,輸出錯(cuò)誤信息。
async 函數(shù)中的 return x 表達(dá)式严就,相當(dāng)于 return await x 的簡(jiǎn)寫总寻。
/**
* await 后面執(zhí)行成功
*/
async function awaitFuncSuccess() {
const n1 = await 10;
const n2 = await new Promise<number>((resolve) => {
setTimeout(() => {
resolve(20);
}, 2000);
});
console.log('n1', n1, 'n2', n2);
return n1 * n2;
}
awaitFuncSuccess()
.then(
(res) => {
console.log('執(zhí)行成功', res); // 約兩秒后 輸出 200
}
)
.catch(
(error) => {
console.log('error', error);
}
);
/**
* await 后面執(zhí)行失敗
*/
async function awaitFuncFail() {
const n1 = await 10;
console.log('n1', n1);
const n2 = await new Promise<number>((resolve, reject) => {
setTimeout(() => {
reject(20);
}, 2000);
});
console.log('n1', n1, 'n2', n2);
return n1 * n2;
}
awaitFuncFail()
.then(
(res) => {
console.log('執(zhí)行成功', res);
}
)
.catch(
(error) => {
console.log('error', error); // 約兩秒后 輸出 20
}
);
順序發(fā)生
/**
* 順序執(zhí)行
*/
async function queueFunc_A() {
const n1 = await createPromise();
console.log('n1', n1);
const n2 = await createPromise();
console.log('n2', n2);
const n3 = await createPromise();
console.log('n3', n3);
}
function createPromise() {
return new Promise((resolve) => {
setTimeout(() => {
setTimeout(() => {
resolve(10);
});
}, 2000);
});
}
async function queueFunc_B() {
for (let i = 0; i < 3; i ++) {
let n = await createPromise();
console.log('N' + (i + 1), n);
}
}
// 運(yùn)行下面兩個(gè)函數(shù),都是間隔兩秒依次輸出
queueFunc_A();
queueFunc_B();
并發(fā)執(zhí)行
仔細(xì)查看下面的代碼是如何使用數(shù)組進(jìn)行并行執(zhí)行的
/**
* 并行執(zhí)行
*/
async function parallelFunc_A() {
const res = await Promise.all([createPromise(), createPromise(), createPromise()]);
console.log('Data', res);
}
async function parallelFunc_B() {
let res = [];
const reqs = [createPromise(), createPromise(), createPromise()];
for (let i = 0; i < reqs.length; i++) {
res[i] = await reqs[i];
}
console.log('Data', res);
}
async function parallelFunc_C() {
let res = [];
let reqs = [1,2,3].map(
async (item) => {
let n = await createPromise();
return n + 1;
}
);
for (let i = 0; i < reqs.length; i++) {
res[i] = await reqs[i]
}
console.log('Data', res);
}
錯(cuò)誤處理
一旦 await 后面的 Promise 轉(zhuǎn)變成 rejected, 整個(gè) async 函數(shù)便會(huì)終止梢为。然而很多時(shí)候我們不希望因?yàn)槟硞€(gè)異步操作的失敗渐行,就終止整個(gè)函數(shù),因此需要進(jìn)行合理錯(cuò)誤處理抖誉。注意殊轴,這里所說(shuō)的錯(cuò)誤不包括引擎解析或執(zhí)行的錯(cuò)誤,僅僅是狀態(tài)變?yōu)?rejected 的 Promise 對(duì)象
處理錯(cuò)誤的方式有兩種: 一種是對(duì) Promise 對(duì)象進(jìn)行包裝袒炉,使其始終返回一個(gè)成功的 Promise 旁理。 二是使用 try/catch 捕獲錯(cuò)誤。
/**
* 錯(cuò)誤處理
*/
async function errorFunc_A() {
let n;
n = await createPromiseByParam(true);
return n;
}
async function errorFunc_B() {
let n;
try {
n = await createPromiseByParam(false);
} catch (e) {
n = e;
}
return n;
}
function createPromiseByParam(param: boolean) {
const p = new Promise((resolve, reject) => {
setTimeout(() => {
reject('出錯(cuò)啦我磁!')
}, 1000);
});
return param ? p.catch((error) => { return 'catch error' + error}) : p;
}
// async 返回的是一個(gè) Promise 對(duì)象孽文,執(zhí)行下面的函數(shù)獲取結(jié)果
errorFunc_A().then(console.log);
errorFunc_B().then(console.log);