前端非同步操作大雜燴(ajax、fetch、promise、async/await)
一、為什麼需要非同步操作?
非同步操作的主要應用場景有兩個: 1)向後臺請求資料 2)需要複雜的計算 如果所有的操作是同步的,那麼就會出現一個很重要的問題,在等待後臺伺服器傳回來的資料和進行復雜計算的時候,使用者介面不能進行任何操作,使用者處於等待狀態,這不利於互動。
二、伺服器互動的非同步(Ajax---->Promise---->Fetch)
簡單來說,需要去後臺查詢資料庫,拿到資料再進行下一步操作。需要注意下面兩種情況 1)不能讓使用者等待,網路之間傳遞資料,可能存在網路延遲,資料庫操作失敗等等意外情況; 2)不要重新整理頁面,web的工作原理,一個http請求需要一個頁面,如果需要再重新整理頁面,會打斷使用者。
2.1 Ajax:
為了上面的兩個目的,有大神想出了ajax(Asynchronous JavaScript and XML)——Javascript非同步執行網路請求。
ajax程式碼: var XHR = new XMLHttpRequest(); XHR.onreadystatechange = function() { if (XHR.readyState == 4 && XHR.status == 200) { //為什麼需要兩個來判斷?如果只使用readystate,如果伺服器請求出錯,也會返回訊息,這不是我們想要的結果,如果只是用狀態碼判斷,那麼會響應好幾次 result = XHR.response; console.log(result);//返回結果通過回撥函式 } } XHR.open('GET', url, true); XHR.send();//get請求不需要傳送資料,post需要傳送資料,以字串或者Formdata的形式傳送
上面的操作,對於單個http請求沒有什麼問題,但是如果我需要根據結果再進行http請求,會發生圖片所示的回撥地獄,導致程式碼可讀性不好
2.2 Promise
為了解決上面的回撥地獄,前端工作者們,想出了promise這個方法,來簡化寫法。 Promise,翻譯過來就是承諾,就好比我承諾了一件事情,如果辦成了會怎麼樣,沒辦成會怎麼樣,正在辦會怎麼樣。正好對應Promise的三種狀態,pending、resolve、reject。每一種狀態都是不可逆的,只能從pending->resolve或者從pending->reject(既然承諾了,事情總要有個交代)。 對應結果resolve和reject都有對應的回撥函式(你需要向委託方回覆)。 從上面可以看出,Promise是一個關注結果的。 語法上講,promise就是一個物件,可以直接new一個
// new一個promsie物件
const promise = new Promise((resolve, reject) => { if (condition) {
resolve(value);
}
if (error) {
reject (error);
}
}
// 指定結果狀態的回撥函式
promise.then(
(value) => {
//do comething
},
(error) => {
//do something
}
);
是不是很簡單,那麼對於非同步請求的promsie寫法應該是什麼樣子的?
// 封裝請求函式
function getData() {
return new Promise( (resolve, reject) => {
var XHR = new XMLHttpRequest();
XHR.onreadystatechange = function() {
if(this.readyState != 4){
return;
}
if (XHR.readyState == 4 && XHR.status == 200) {
result = XHR.response;
resolve(result);
} else{
reject(new Error(this.statusText));
}
}
XHR.open('GET', url, true);
XHR.send();
})
}
getData (url).then(() = > {},() => {});
如果多層巢狀,通過then進行鏈式呼叫就可以了。 Promise還有很多有意思的用法,all、race,需要的自行研究。
2.3 Fetch
既然有promise和ajax,我是不是可以把兩者的優點結合起來,fetch應運而生。fetch呢,其實也沒啥好說的,簡單來說就是ajax的替代品,不過fetch基於Promise的方式,但是它和ajax有亮點不太一樣。 1) 當接收到一個代表錯誤的 HTTP 狀態碼時,從 fetch()返回的 Promise 不會被標記為 reject, 即使該 HTTP 響應的狀態碼是 404 或 500。相反,它會將 Promise 狀態標記為 resolve (但是會將 resolve 的返回值的 ok 屬性設定為 false ),僅當網路故障時或請求被阻止時,才會標記為 reject。 2) 預設情況下,fetch 不會從服務端傳送或接收任何 cookies, 如果站點依賴於使用者 session,則會導致未經認證的請求(要傳送 cookies,必須設定 credentials 選項)。 從語法上,fetch更加簡潔、美觀
fetch('http://example.com/movies.json')
.then(function(response) {
return response.json(); //fetch需要手動轉化為json,不然拿不到
})
.then(function(myJson) {
console.log(myJson);
}
2.4 async/await
雖然使用了fetch,但是還是有回撥的影子,能不能再美觀一下,async/await出來了,async/await就是promise的語法糖,可以讓非同步和同步編寫一樣.
語法上是這個樣子的
try {
let response = await fetch(url);
let data = await response.json();
console.log(data);
} catch(e) {
console.log("Oops, error", e);
}
三、實戰利器
場景描述: 使用者按鈕操作,需要向後臺請求兩個介面,拿到介面資料後,再顯示到前端頁面。
// 假設向後臺請求資料的方式為fetchData
// fetchData (type, url, data),
// 為什麼採用引數的形式而不是採用物件的方式
// {
type :type ,url: url ,data: data
}
// 因為堅信 約定大約配置,簡化程式碼編寫
async () => {
let result = [];
const dataSource = [
[get, url1, {id: 123}],
[post, url2, {}]
];
// 考慮下下面的迴圈能不能用foreach或者map
for (let i = 0, len = dataSource.length; i < len ; i++) {
result[i] = await fetchData(...dataSource[i]);
}
/**
另一種方式,which one U pick?
let promises = dataSource.map((item) => getData(...item));
let result = await Promise.all(promises).catch(function(err){
console.log(err);
});;
**/
// 根據result中的結果進行相應的操作就可以了
}