前端异步操作大杂烩(ajax、fetch、promise、async/await)

时间:2024-03-28 20:21:32


前端工作中面临两个重要的问题:同步与异步操作。
  同步操作,用户操作即时显示的,比如切换菜单,按钮点击效果等等。
  异步操作,需要和服务器交互,需要一定处理时间的,比如显示查询操作等等。
异步操作主要通过回调函数实现,所谓的回调就是,“回过头来再调用”,就和你在外面吃饭叫号一样。

一、为什么需要异步操作?

异步操作的主要应用场景有两个:
  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请求,会发生图片所示的回调地狱,导致代码可读性不好
前端异步操作大杂烩(ajax、fetch、promise、async/await)

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中的结果进行相应的操作就可以了
}

参考链接

Promise
Fetch

附录

xmlhttprequest状态码:
0:初始化,XMLHttpRequest对象还没有完成初始化
1:载入,XMLHttpRequest对象开始发送请求
2:载入完成,XMLHttpRequest对象的请求发送完成
3:解析,XMLHttpRequest对象开始读取服务器的响应
4:完成,XMLHttpRequest对象读取服务器响应结束
http请求状态码:
1xx:信息响应类,表示接收到请求并且继续处理
2xx:处理成功响应类,表示动作被成功接收、理解和接受
3xx:重定向响应类,为了完成指定的动作,必须接受进一步处理
4xx:客户端错误,客户请求包含语法错误或者是不能正确执行
5xx:服务端错误,服务器不能正确执行一个正确的请求

100——客户必须继续发出请求
101——客户要求服务器根据请求转换HTTP协议版本
200——交易成功
201——提示知道新文件的URL
202——接受和处理、但处理未完成
203——返回信息不确定或不完整
204——请求收到,但返回信息为空
205——服务器完成了请求,用户代理必须复位当前已经浏览过的文件
206——服务器已经完成了部分用户的GET请求
300——请求的资源可在多处得到
301——删除请求数据
302——在其他地址发现了请求数据
303——建议客户访问其他URL或访问方式
304——客户端已经执行了GET,但文件未变化
305——请求的资源必须从服务器指定的地址得到
306——前一版本HTTP中使用的代码,现行版本中不再使用
307——申明请求的资源临时性删除
400——错误请求,如语法错误
401——请求授权失败
402——保留有效ChargeTo头响应
403——请求不允许
404——没有发现文件、查询或URl
405——用户在Request-Line字段定义的方法不允许
406——根据用户发送的Accept拖,请求资源不可访问
407——类似401,用户必须首先在代理服务器上得到授权
408——客户端没有在用户指定的饿时间内完成请求
409——对当前资源状态,请求不能完成
410——服务器上不再有此资源且无进一步的参考地址
411——服务器拒绝用户定义的Content-Length属性请求
412——一个或多个请求头字段在当前请求中错误
413——请求的资源大于服务器允许的大小
414——请求的资源URL长于服务器允许的长度
415——请求资源不支持请求项目格式
416——请求中包含Range请求头字段,在当前请求资源范围内没有range指示值,请求也不包含If-Range请求头字段
417——服务器不满足请求Expect头字段指定的期望值,如果是代理服务器,可能是下一级服务器不能满足请求
500——服务器产生内部错误
501——服务器不支持请求的函数
502——服务器暂时不可用,有时是为了防止发生系统过载
503——服务器过载或暂停维修
504——关口过载,服务器使用另一个关口或服务来响应用户,等待时间设定值较长
505——服务器不支持或拒绝支请求头中指定的HTTP版本