1. 程式人生 > >前端非同步操作大雜燴(ajax、fetch、promise、async/await)

前端非同步操作大雜燴(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中的結果進行相應的操作就可以了
}

參考連結