異步調(diào)用
異步
JavaScript的執(zhí)行環(huán)境是單線(xiàn)程。
所謂單線(xiàn)程,是指JS引擎中負(fù)責(zé)解釋和執(zhí)行JavaScript代碼的線(xiàn)程只有一個(gè),也就是一次只能完成一項(xiàng)任務(wù),這個(gè)任務(wù)執(zhí)行完后才能執(zhí)行下一個(gè),它會(huì)「阻塞」其他任務(wù)。這個(gè)任務(wù)可稱(chēng)為主線(xiàn)程。
異步模式可以一起執(zhí)行多個(gè)任務(wù)。
常見(jiàn)的異步模式有以下幾種:
定時(shí)器
接口調(diào)用
事件函數(shù)
今天這篇文章,我們重點(diǎn)講一下接口調(diào)用。接口調(diào)用里,重點(diǎn)講一下Promise。
接口調(diào)用的方式
js 中常見(jiàn)的接口調(diào)用方式,有以下幾種:
- 原生ajax
- 基于jQuery的ajax
- Fetch
- Promise
- axios
多次異步調(diào)用的依賴(lài)分析
多次異步調(diào)用的結(jié)果,順序可能不同步。
異步調(diào)用的結(jié)果如果存在依賴(lài),則需要嵌套。
在ES5中,當(dāng)進(jìn)行多層嵌套回調(diào)時(shí),會(huì)導(dǎo)致代碼層次過(guò)多,很難進(jìn)行維護(hù)和二次開(kāi)發(fā);而且會(huì)導(dǎo)致回調(diào)地獄的問(wèn)題。ES6中的Promise 就可以解決這兩個(gè)問(wèn)題。
Promise 概述
Promise的介紹和優(yōu)點(diǎn)
ES6中的Promise 是異步編程的一種方案。從語(yǔ)法上講,Promise 是一個(gè)對(duì)象,它可以獲取異步操作的消息。
Promise對(duì)象, 可以將異步操作以同步的流程表達(dá)出來(lái)。使用 Promise 主要有以下好處:
可以很好地解決回調(diào)地獄的問(wèn)題(避免了層層嵌套的回調(diào)函數(shù))。
語(yǔ)法非常簡(jiǎn)潔。Promise 對(duì)象提供了簡(jiǎn)潔的API,使得控制異步操作更加容易。
回調(diào)地獄的舉例
假設(shè)買(mǎi)菜、做飯、洗碗都是異步的。
但真實(shí)的場(chǎng)景中,實(shí)際的操作流程是:買(mǎi)菜成功之后,才能開(kāi)始做飯。做飯成功后,才能開(kāi)始洗碗。這里面就涉及到了多層嵌套調(diào)用,也就是回調(diào)地獄。
Promise 的基本用法
(1)使用new實(shí)例化一個(gè)Promise對(duì)象,Promise的構(gòu)造函數(shù)中傳遞一個(gè)參數(shù)。這個(gè)參數(shù)是一個(gè)函數(shù),該函數(shù)用于處理異步任務(wù)。
(2)并且傳入兩個(gè)參數(shù):resolve和reject,分別表示異步執(zhí)行成功后的回調(diào)函數(shù)和異步執(zhí)行失敗后的回調(diào)函數(shù);
(3)通過(guò) promise.then() 處理返回結(jié)果。這里的 p 指的是 Promise實(shí)例。
代碼舉例如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
// 第一步:model層的接口封裝
const promise = new Promise((resolve, reject) => {
// 這里做異步任務(wù)(比如ajax 請(qǐng)求接口。這里暫時(shí)用定時(shí)器代替)
setTimeout(function() {
var data = { retCode: 0, msg: 'qianguyihao' }; // 接口返回的數(shù)據(jù)
if (data.retCode == 0) {
// 接口請(qǐng)求成功時(shí)調(diào)用
resolve(data);
} else {
// 接口請(qǐng)求失敗時(shí)調(diào)用
reject({ retCode: -1, msg: 'network error' });
}
}, 100);
});
// 第二步:業(yè)務(wù)層的接口調(diào)用。這里的 data 就是 從 resolve 和 reject 傳過(guò)來(lái)的,也就是從接口拿到的數(shù)據(jù)
promise.then(data => {
// 從 resolve 獲取正常結(jié)果
console.log(data);
}).catch(data => {
// 從 reject 獲取異常結(jié)果
console.log(data);
});
</script>
</body>
</html>
上方代碼中,當(dāng)從接口返回的數(shù)據(jù)data.retCode的值不同時(shí),可能會(huì)走resolve,也可能會(huì)走 reject,這個(gè)由你自己的業(yè)務(wù)決定。
promise對(duì)象的3個(gè)狀態(tài)(了解即可)
初始化狀態(tài)(等待狀態(tài)):pending
成功狀態(tài):fullfilled
失敗狀態(tài):rejected
(1)當(dāng)new Promise()執(zhí)行之后,promise對(duì)象的狀態(tài)會(huì)被初始化為pending,這個(gè)狀態(tài)是初始化狀態(tài)。new Promise()這行代碼,括號(hào)里的內(nèi)容是同步執(zhí)行的。括號(hào)里定義一個(gè)function,function有兩個(gè)參數(shù):resolve和reject。如下:如果請(qǐng)求成功了,則執(zhí)行resolve(),此時(shí),promise的狀態(tài)會(huì)被自動(dòng)修改為fullfilled。
如果請(qǐng)求失敗了,則執(zhí)行reject(),此時(shí),promise的狀態(tài)會(huì)被自動(dòng)修改為rejected
(2)promise.then()方法,括號(hào)里面有兩個(gè)參數(shù),分別代表兩個(gè)函數(shù) function1 和 function2:
如果promise的狀態(tài)為fullfilled(意思是:如果請(qǐng)求成功),則執(zhí)行function1里的內(nèi)容
如果promise的狀態(tài)為rejected(意思是,如果請(qǐng)求失敗),則執(zhí)行function2里的內(nèi)容
另外,resolve()和reject()這兩個(gè)方法,是可以給promise.then()傳遞參數(shù)的。
完整代碼舉例如下:
let promise = new Promise((resolve, reject) => {
//進(jìn)來(lái)之后,狀態(tài)為pending
console.log('111'); //這行代碼是同步的
//開(kāi)始執(zhí)行異步操作(這里開(kāi)始,寫(xiě)異步的代碼,比如ajax請(qǐng)求 or 開(kāi)啟定時(shí)器)
if (異步的ajax請(qǐng)求成功) {
console.log('333');
resolve('haha');//如果請(qǐng)求成功了,請(qǐng)寫(xiě)resolve(),此時(shí),promise的狀態(tài)會(huì)被自動(dòng)修改為fullfilled
} else {
reject('555');//如果請(qǐng)求失敗了,請(qǐng)寫(xiě)reject(),此時(shí),promise的狀態(tài)會(huì)被自動(dòng)修改為rejected
}
})
console.log('222');
//調(diào)用promise的then()
promise.then((successMsg) => {
//如果promise的狀態(tài)為fullfilled,則執(zhí)行這里的代碼
console.log(successMsg, '成功了');
}
, (errorMsg) => {
//如果promise的狀態(tài)為rejected,則執(zhí)行這里的代碼
console.log(errorMsg, '失敗了');
}
)
基于 Promise 處理 多次 Ajax 請(qǐng)求(鏈?zhǔn)秸{(diào)用)【重要】
實(shí)際開(kāi)發(fā)中,我們經(jīng)常需要同時(shí)請(qǐng)求多個(gè)接口。比如說(shuō):在請(qǐng)求完接口1的數(shù)據(jù)data1之后,需要根據(jù)data1的數(shù)據(jù),繼續(xù)請(qǐng)求接口2,獲取data2;然后根據(jù)data2的數(shù)據(jù),繼續(xù)請(qǐng)求接口3。
這種場(chǎng)景其實(shí)就是接口的多層嵌套調(diào)用。有了 promise之后,我們可以把多層嵌套調(diào)用按照線(xiàn)性的方式進(jìn)行書(shū)寫(xiě),非常優(yōu)雅。
也就是說(shuō):Promise 可以把原本的多層嵌套調(diào)用改進(jìn)為鏈?zhǔn)秸{(diào)用。
代碼舉例:(多次 Ajax請(qǐng)求,鏈?zhǔn)秸{(diào)用)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script type="text/javascript">
/*
基于Promise發(fā)送Ajax請(qǐng)求
*/
function queryData(url) {
var promise = new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState != 4) return;
if (xhr.readyState == 4 && xhr.status == 200) {
// 處理正常情況
resolve(xhr.responseText); // xhr.responseText 是從接口拿到的數(shù)據(jù)
} else {
// 處理異常情況
reject('接口請(qǐng)求失敗');
}
};
xhr.responseType = 'json'; // 設(shè)置返回的數(shù)據(jù)類(lèi)型
xhr.open('get', url);
xhr.send(null); // 請(qǐng)求接口
});
return promise;
}
// 發(fā)送多個(gè)ajax請(qǐng)求并且保證順序
queryData('http://localhost:3000/api1')
.then(
data1 => {
console.log(JSON.stringify(data1));
// 請(qǐng)求完接口1后,繼續(xù)請(qǐng)求接口2
return queryData('http://localhost:3000/api2');
},
error1 => {
console.log(error1);
}
)
.then(
data2 => {
console.log(JSON.stringify(data2));
// 請(qǐng)求完接口2后,繼續(xù)請(qǐng)求接口3
return queryData('http://localhost:3000/api3');
},
error2 => {
console.log(error2);
}
)
.then(
data3 => {
// 獲取接口3返回的數(shù)據(jù)
console.log(JSON.stringify(data3));
},
error3 => {
console.log(error3);
}
);
</script>
</body>
</html>
return 的函數(shù)返回值
return 后面的返回值,有兩種情況:
情況1:返回 Promise 實(shí)例對(duì)象。返回的該實(shí)例對(duì)象會(huì)調(diào)用下一個(gè) then。
情況2:返回普通值。返回的普通值會(huì)直接傳遞給下一個(gè)then,通過(guò) then 參數(shù)中函數(shù)的參數(shù)接收該值。
我們針對(duì)上面這兩種情況,詳細(xì)解釋一下。
情況1:返回 Promise 實(shí)例對(duì)象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script type="text/javascript">
/*
基于Promise發(fā)送Ajax請(qǐng)求
*/
function queryData(url) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState != 4) return;
if (xhr.readyState == 4 && xhr.status == 200) {
// 處理正常情況
resolve(xhr.responseText);
} else {
// 處理異常情況
reject('接口請(qǐng)求失敗');
}
};
xhr.responseType = 'json'; // 設(shè)置返回的數(shù)據(jù)類(lèi)型
xhr.open('get', url);
xhr.send(null); // 請(qǐng)求接口
});
}
// 發(fā)送多個(gè)ajax請(qǐng)求并且保證順序
queryData('http://localhost:3000/api1')
.then(
data1 => {
console.log(JSON.stringify(data1));
return queryData('http://localhost:3000/api2');
},
error1 => {
console.log(error1);
}
)
.then(
data2 => {
console.log(JSON.stringify(data2));
// 這里的 return,返回的是 Promise 實(shí)例對(duì)象
return new Promise((resolve, reject) => {
resolve('qianguyihao');
});
},
error2 => {
console.log(error2);
}
)
.then(data3 => {
console.log(data3);
});
</script>
</body>
</html>
情況2:返回 普通值
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script type="text/javascript">
/*
基于Promise發(fā)送Ajax請(qǐng)求
*/
function queryData(url) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState != 4) return;
if (xhr.readyState == 4 && xhr.status == 200) {
// 處理正常情況
resolve(xhr.responseText);
} else {
// 處理異常情況
reject('接口請(qǐng)求失敗');
}
};
xhr.responseType = 'json'; // 設(shè)置返回的數(shù)據(jù)類(lèi)型
xhr.open('get', url);
xhr.send(null); // 請(qǐng)求接口
});
}
// 發(fā)送多個(gè)ajax請(qǐng)求并且保證順序
queryData('http://localhost:3000/api1')
.then(
data1 => {
console.log(JSON.stringify(data1));
return queryData('http://localhost:3000/api2');
},
error1 => {
console.log(error1);
}
)
.then(
data2 => {
console.log(JSON.stringify(data2));
// 返回普通值
return 'qianguyihao';
},
error2 => {
console.log(error2);
}
)
/*
既然上方返回的是 普通值,那么,這里的 then 是誰(shuí)來(lái)調(diào)用呢?
答案是:這里會(huì)產(chǎn)生一個(gè)新的 默認(rèn)的 promise實(shí)例,來(lái)調(diào)用這里的then,確??梢岳^續(xù)進(jìn)行鏈?zhǔn)讲僮鳌? */
.then(data3 => {
// 這里的 data3 接收的是 普通值 'qianguyihao'
console.log(data3);
});
</script>
</body>
</html>
Promise 的常用API:實(shí)例方法【重要】
Promise 自帶的API提供了如下實(shí)例方法:
promise.then():獲取異步任務(wù)的正常結(jié)果。
promise.catch():獲取異步任務(wù)的異常結(jié)果。
promise.finaly():異步任務(wù)無(wú)論成功與否,都會(huì)執(zhí)行。
代碼舉例如下。
寫(xiě)法1:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
function queryData() {
return new Promise((resolve, reject) => {
setTimeout(function() {
var data = { retCode: 0, msg: 'qianguyihao' }; // 接口返回的數(shù)據(jù)
if (data.retCode == 0) {
// 接口請(qǐng)求成功時(shí)調(diào)用
resolve(data);
} else {
// 接口請(qǐng)求失敗時(shí)調(diào)用
reject({ retCode: -1, msg: 'network error' });
}
}, 100);
});
}
queryData()
.then(data => {
// 從 resolve 獲取正常結(jié)果
console.log('接口請(qǐng)求成功時(shí),走這里');
console.log(data);
})
.catch(data => {
// 從 reject 獲取異常結(jié)果
console.log('接口請(qǐng)求失敗時(shí),走這里');
console.log(data);
})
.finally(() => {
console.log('無(wú)論接口請(qǐng)求成功與否,都會(huì)走這里');
});
</script>
</body>
</html>
寫(xiě)法2:(和上面的寫(xiě)法1等價(jià))
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
function queryData() {
return new Promise((resolve, reject) => {
setTimeout(function() {
var data = { retCode: 0, msg: 'qianguyihao' }; // 接口返回的數(shù)據(jù)
if (data.retCode == 0) {
// 接口請(qǐng)求成功時(shí)調(diào)用
resolve(data);
} else {
// 接口請(qǐng)求失敗時(shí)調(diào)用
reject({ retCode: -1, msg: 'network error' });
}
}, 100);
});
}
queryData()
.then(
data => {
// 從 resolve 獲取正常結(jié)果
console.log('接口請(qǐng)求成功時(shí),走這里');
console.log(data);
},
data => {
// 從 reject 獲取異常結(jié)果
console.log('接口請(qǐng)求失敗時(shí),走這里');
console.log(data);
}
)
.finally(() => {
console.log('無(wú)論接口請(qǐng)求成功與否,都會(huì)走這里');
});
</script>
</body>
</html>
注意:寫(xiě)法1和寫(xiě)法2的作用是完全等價(jià)的。只不過(guò),寫(xiě)法2是把 catch 里面的代碼作為 then里面的第二個(gè)參數(shù)而已。
Promise 的常用API:對(duì)象方法【重要】
Promise 自帶的API提供了如下對(duì)象方法:
Promise.all():并發(fā)處理多個(gè)異步任務(wù),所有任務(wù)都執(zhí)行成功,才能得到結(jié)果。
Promise.race(): 并發(fā)處理多個(gè)異步任務(wù),只要有一個(gè)任務(wù)執(zhí)行成功,就能得到結(jié)果。
下面來(lái)詳細(xì)介紹。
Promise.all() 代碼舉例
代碼舉例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script type="text/javascript">
/*
封裝 Promise 接口調(diào)用
*/
function queryData(url) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState != 4) return;
if (xhr.readyState == 4 && xhr.status == 200) {
// 處理正常結(jié)果
resolve(xhr.responseText);
} else {
// 處理異常結(jié)果
reject('服務(wù)器錯(cuò)誤');
}
};
xhr.open('get', url);
xhr.send(null);
});
}
var promise1 = queryData('http://localhost:3000/a1');
var promise2 = queryData('http://localhost:3000/a2');
var promise3 = queryData('http://localhost:3000/a3');
Promise.all([promise1, promise2, promise3]).then(result => {
console.log(result);
});
</script>
</body>
</html>
Promise.race() 代碼舉例
代碼舉例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script type="text/javascript">
/*
封裝 Promise 接口調(diào)用
*/
function queryData(url) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState != 4) return;
if (xhr.readyState == 4 && xhr.status == 200) {
// 處理正常結(jié)果
resolve(xhr.responseText);
} else {
// 處理異常結(jié)果
reject('服務(wù)器錯(cuò)誤');
}
};
xhr.open('get', url);
xhr.send(null);
});
}
var promise1 = queryData('http://localhost:3000/a1');
var promise2 = queryData('http://localhost:3000/a2');
var promise3 = queryData('http://localhost:3000/a3');
Promise.race([promise1, promise2, promise3]).then(result => {
console.log(result);
});
</script>
</body>
</html>