1. 概述
在上一篇文章《JavaScript異步編程1——Promise的初步使用》,簡單介紹了一下Promise的初步使用。復習一下,Promise異步編程可以用如下的范式來編寫:
- 定義一個函數(function A),這個函數返回一個Promise對象。
- Promise對象的參數也是一個function B,內部進行了一個異步操作(通常是JavaScript提供的API)。
- function B對象的參數是兩個回調函數resolve和reject。如果函數內部進行的異步操作成功,回調resolve;否則回調reject。
- 調用function A,返回一個Promise對象,這樣異步操作就啟動了。
- 調用Promise對象的then方法,參數是resolve和reject的真正響應函數。當異步操作完成了,就會執行相應分支的響應函數。
采用以上范式,可以通過Promise來進行Ajax操作,也就是XMLHttpRequest,畢竟這個操作在Web應用中實在太常見了。
2. 詳論
首先仍然是准備一個HTML頁面:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="./3rdParty/jquery-3.5.1.js"></script>
<title>樣例</title>
</head>
<body>
<div id = "container"> </div>
<script src="./PromiseTest.js"></script>
</body>
</html>
如果不使用Promise,那么相應的JavaScript代碼為:
$(function () {
var url = "./1.json";
var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function () {
if (req.status == 200) {
console.log(req.response);
} else {
throw new Error(req.statusText);
}
};
req.onerror = function () {
throw new Error("Network Error");
};
req.send();
});
可以看到這里仍然是通過事件機制來實現異步行為。接下來采用前面提到的編程范式將其改造成Promise機制:
function get(url) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function () {
//即使是404也會進入這個相應函數,所以需要檢測狀態
if (req.status == 200) {
//完成許諾,返回響應文本
resolve(req.response);
} else {
//完成未完成,返回錯誤
reject(Error(req.statusText));
}
};
// 發生錯誤時的相應函數
req.onerror = function () {
reject(Error("Network Error"));
};
// 發送請求
req.send();
});
}
var addressUri = "./1.json";
get(addressUri).then(function (response) {
console.log("Success!", response);
}, function (error) {
console.error("Failed!", error);
});
改造成Promise的過程與上一章並沒有什么不同,只不過函數內部調用XMLHttpRequest的流程更加復雜些。這兩個例子都是將事件改造成Promise,那不是意味着對於異步編程而言,Promise要優於事件呢?
不能完全這么肯定,但是可以確定的是事件並不總是異步編程的最優實踐。一個很顯然的問題就是:事件很適合處理在同一對象上多次發生的事情,但是事件偵聽器的響應函數可能並不是我們想要的——更多情況下,我們只想要直到兩個狀態,當異步操作完成的時候該做什么,當異步操作失敗的時候又該做什么,而這正是Promise擅長處理的。
例如這里的XMLHttpRequest操作,事件響應函數onload中的所有行為,並不都是異步請求成功時需要完成的,只有檢測訪問請求狀態為200時候,才需要進行請求成功時的回調函數。使用Promise,可以更准確的進行異步行為。