非同步調用 非同步 JavaScript的執行環境是 單線程 。 所謂單線程,是指JS引擎中負責解釋和執行JavaScript代碼的線程只有一個,也就是一次只能完成一項任務,這個任務執行完後才能執行下一個,它會「阻塞」其他任務。這個任務可稱為主線程。 非同步模式可以一起執行 多個任務 。 常見的非同步模式有 ...
非同步調用
非同步
JavaScript的執行環境是單線程。
所謂單線程,是指JS引擎中負責解釋和執行JavaScript代碼的線程只有一個,也就是一次只能完成一項任務,這個任務執行完後才能執行下一個,它會「阻塞」其他任務。這個任務可稱為主線程。
非同步模式可以一起執行多個任務。
常見的非同步模式有以下幾種:
-
定時器
-
介面調用
-
事件函數
今天這篇文章,我們重點講一下介面調用。介面調用里,重點講一下Promise。
介面調用的方式
js 中常見的介面調用方式,有以下幾種:
- 原生ajax
- 基於jQuery的ajax
- Fetch
- Promise
- axios
多次非同步調用的依賴分析
-
多次非同步調用的結果,順序可能不同步。
-
非同步調用的結果如果存在依賴,則需要嵌套。
在ES5中,當進行多層嵌套回調時,會導致代碼層次過多,很難進行維護和二次開發;而且會導致回調地獄的問題。ES6中的Promise 就可以解決這兩個問題。
Promise 概述
Promise的介紹和優點
ES6中的Promise 是非同步編程的一種方案。從語法上講,Promise 是一個對象,它可以獲取非同步操作的消息。
Promise對象, 可以將非同步操作以同步的流程表達出來。使用 Promise 主要有以下好處:
-
可以很好地解決回調地獄的問題(避免了層層嵌套的回調函數)。
-
語法非常簡潔。Promise 對象提供了簡潔的API,使得控制非同步操作更加容易。
回調地獄的舉例
假設買菜、做飯、洗碗都是非同步的。
但真實的場景中,實際的操作流程是:買菜成功之後,才能開始做飯。做飯成功後,才能開始洗碗。這裡面就涉及到了多層嵌套調用,也就是回調地獄。
Promise 的基本用法
(1)使用new實例化一個Promise對象,Promise的構造函數中傳遞一個參數。這個參數是一個函數,該函數用於處理非同步任務。
(2)並且傳入兩個參數:resolve和reject,分別表示非同步執行成功後的回調函數和非同步執行失敗後的回調函數;
(3)通過 promise.then() 處理返回結果。這裡的 p 指的是 Promise實例。
代碼舉例如下:
<!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) => {
// 這裡做非同步任務(比如ajax 請求介面。這裡暫時用定時器代替)
setTimeout(function() {
var data = { retCode: 0, msg: 'qianguyihao' }; // 介面返回的數據
if (data.retCode == 0) {
// 介面請求成功時調用
resolve(data);
} else {
// 介面請求失敗時調用
reject({ retCode: -1, msg: 'network error' });
}
}, 100);
});
// 第二步:業務層的介面調用。這裡的 data 就是 從 resolve 和 reject 傳過來的,也就是從介面拿到的數據
promise.then(data => {
// 從 resolve 獲取正常結果
console.log(data);
}).catch(data => {
// 從 reject 獲取異常結果
console.log(data);
});
</script>
</body>
</html>
上方代碼中,當從介面返回的數據data.retCode
的值不同時,可能會走 resolve,也可能會走 reject,這個由你自己的業務決定。
promise對象的3個狀態(瞭解即可)
-
初始化狀態(等待狀態):pending
-
成功狀態:fullfilled
-
失敗狀態:rejected
(1)當new Promise()執行之後,promise對象的狀態會被初始化為pending
,這個狀態是初始化狀態。new Promise()
這行代碼,括弧里的內容是同步執行的。括弧里定義一個function,function有兩個參數:resolve和reject。如下:
-
如果請求成功了,則執行resolve(),此時,promise的狀態會被自動修改為fullfilled。
-
如果請求失敗了,則執行reject(),此時,promise的狀態會被自動修改為rejected
(2)promise.then()方法,括弧裡面有兩個參數,分別代表兩個函數 function1 和 function2:
-
如果promise的狀態為fullfilled(意思是:如果請求成功),則執行function1里的內容
-
如果promise的狀態為rejected(意思是,如果請求失敗),則執行function2里的內容
另外,resolve()和reject()這兩個方法,是可以給promise.then()傳遞參數的。
完整代碼舉例如下:
let promise = new Promise((resolve, reject) => {
//進來之後,狀態為pending
console.log('111'); //這行代碼是同步的
//開始執行非同步操作(這裡開始,寫非同步的代碼,比如ajax請求 or 開啟定時器)
if (非同步的ajax請求成功) {
console.log('333');
resolve('haha');//如果請求成功了,請寫resolve(),此時,promise的狀態會被自動修改為fullfilled
} else {
reject('555');//如果請求失敗了,請寫reject(),此時,promise的狀態會被自動修改為rejected
}
})
console.log('222');
//調用promise的then()
promise.then((successMsg) => {
//如果promise的狀態為fullfilled,則執行這裡的代碼
console.log(successMsg, '成功了');
}
, (errorMsg) => {
//如果promise的狀態為rejected,則執行這裡的代碼
console.log(errorMsg, '失敗了');
}
)
基於 Promise 處理 多次 Ajax 請求(鏈式調用)【重要】
實際開發中,我們經常需要同時請求多個介面。比如說:在請求完介面1
的數據data1
之後,需要根據data1
的數據,繼續請求介面2,獲取data2
;然後根據data2
的數據,繼續請求介面3。
這種場景其實就是介面的多層嵌套調用。有了 promise之後,我們可以把多層嵌套調用按照線性的方式進行書寫,非常優雅。
也就是說:Promise 可以把原本的多層嵌套調用改進為鏈式調用。
代碼舉例:(多次 Ajax請求,鏈式調用)
<!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發送Ajax請求
*/
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 是從介面拿到的數據
} else {
// 處理異常情況
reject('介面請求失敗');
}
};
xhr.responseType = 'json'; // 設置返回的數據類型
xhr.open('get', url);
xhr.send(null); // 請求介面
});
return promise;
}
// 發送多個ajax請求並且保證順序
queryData('http://localhost:3000/api1')
.then(
data1 => {
console.log(JSON.stringify(data1));
// 請求完介面1後,繼續請求介面2
return queryData('http://localhost:3000/api2');
},
error1 => {
console.log(error1);
}
)
.then(
data2 => {
console.log(JSON.stringify(data2));
// 請求完介面2後,繼續請求介面3
return queryData('http://localhost:3000/api3');
},
error2 => {
console.log(error2);
}
)
.then(
data3 => {
// 獲取介面3返回的數據
console.log(JSON.stringify(data3));
},
error3 => {
console.log(error3);
}
);
</script>
</body>
</html>
上面這個舉例很經典,需要多看幾遍。
return 的函數返回值
return 後面的返回值,有兩種情況:
-
情況1:返回 Promise 實例對象。返回的該實例對象會調用下一個 then。
-
情況2:返回普通值。返回的普通值會直接傳遞給下一個then,通過 then 參數中函數的參數接收該值。
我們針對上面這兩種情況,詳細解釋一下。
情況1:返回 Promise 實例對象
舉例如下:(這個例子,跟上一段 Ajax 鏈式調用 的例子差不多)
<!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發送Ajax請求
*/
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('介面請求失敗');
}
};
xhr.responseType = 'json'; // 設置返回的數據類型
xhr.open('get', url);
xhr.send(null); // 請求介面
});
}
// 發送多個ajax請求並且保證順序
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 實例對象
return new Promise((resolve, reject) => {
resolve('qianguyihao');
});
},
error2 => {
console.log(error2);
}
)
.then(data3 => {
console.log(data3);
});
</script>
</body>
</html>
情況2:返回 普通值
<!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發送Ajax請求
*/
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('介面請求失敗');
}
};
xhr.responseType = 'json'; // 設置返回的數據類型
xhr.open('get', url);
xhr.send(null); // 請求介面
});
}
// 發送多個ajax請求並且保證順序
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 是誰來調用呢?
答案是:這裡會產生一個新的 預設的 promise實例,來調用這裡的then,確保可以繼續進行鏈式操作。
*/
.then(data3 => {
// 這裡的 data3 接收的是 普通值 'qianguyihao'
console.log(data3);
});
</script>
</body>
</html>
Promise 的常用API:實例方法【重要】
Promise 自帶的API提供瞭如下實例方法:
-
promise.then():獲取非同步任務的正常結果。
-
promise.catch():獲取非同步任務的異常結果。
-
promise.finaly():非同步任務無論成功與否,都會執行。
代碼舉例如下。
寫法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' }; // 介面返回的數據
if (data.retCode == 0) {
// 介面請求成功時調用
resolve(data);
} else {
// 介面請求失敗時調用
reject({ retCode: -1, msg: 'network error' });
}
}, 100);
});
}
queryData()
.then(data => {
// 從 resolve 獲取正常結果
console.log('介面請求成功時,走這裡');
console.log(data);
})
.catch(data => {
// 從 reject 獲取異常結果
console.log('介面請求失敗時,走這裡');
console.log(data);
})
.finally(() => {
console.log('無論介面請求成功與否,都會走這裡');
});
</script>
</body>
</html>
寫法2:(和上面的寫法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' }; // 介面返回的數據
if (data.retCode == 0) {
// 介面請求成功時調用
resolve(data);
} else {
// 介面請求失敗時調用
reject({ retCode: -1, msg: 'network error' });
}
}, 100);
});
}
queryData()
.then(
data => {
// 從 resolve 獲取正常結果
console.log('介面請求成功時,走這裡');
console.log(data);
},
data => {
// 從 reject 獲取異常結果
console.log('介面請求失敗時,走這裡');
console.log(data);
}
)
.finally(() => {
console.log('無論介面請求成功與否,都會走這裡');
});
</script>
</body>
</html>
註意:寫法1和寫法2的作用是完全等價的。只不過,寫法2是把 catch 裡面的代碼作為 then裡面的第二個參數而已。
Promise 的常用API:對象方法【重要】
Promise 自帶的API提供瞭如下對象方法:
-
Promise.all():併發處理多個非同步任務,所有任務都執行成功,才能得到結果。
-
Promise.race(): 併發處理多個非同步任務,只要有一個任務執行成功,就能得到結果。
下麵來詳細介紹。
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 介面調用
*/
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('伺服器錯誤');
}
};
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 介面調用
*/
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('伺服器錯誤');
}
};
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>
瞭解這些內容之後, Promise 的基本用法,你就已經掌握了。