一、引言
大家都知道,js是单线程的,但是支持异步,并且在js基础中我们也一起学习了js的执行机制。那么当我们在前台页面使用ajax请求后台服务器的时候,这个过程实际上也是异步操作,所以它们并没有先后顺序可言,我们更无法掌控。
在开发过程中,我们经常会遇到一个ajax依赖另一个ajax请求的数据,那么如果我们按平常的开发思路来进行ajax嵌套调用,那么势必会出现一系列问题,因为它两个ajax都是异步的,所以执行的先后顺序我们完全不知道,那么假如第二个ajax调用了,第一个没有调用,那么就证明第二个ajax没有拿到正儿八经的数据,所以势必会引起一系列的异常和粗错误。
那么如何解决这个问题?ECMAscript 6 原生提供了 Promise 对象,它代表了未来将要发生的事件,用来传递异步操作的消息。那么我们就可以通过Promise 对象来很好的解决ajax依赖嵌套的问题。
二、JavaScript Promise 对象
1、简介
ECMAscript 6 原生提供了 Promise 对象,是异步编程的一种解决方案,它代表了未来将要发生的事件,用来传递异步操作的消息,从它可以获取异步操作的消息。
2、作用
- 可以避免多层异步调用嵌套问题(回调地狱)
- Promise 对象提供了简洁的API,使得控制异步操作更加容易
3、特点
-
对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:
状态 描述 pending 初始状态,不是成功或失败状态。 fulfilled 意味着操作成功完成。 rejected 意味着操作失败。 只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是「承诺」,表示其他手段无法改变。
-
一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
4、优缺点
有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。
Promise 也有一些缺点。首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
5、创建
要想创建一个 promise 对象、可以使用 new 来调用 Promise 的构造器来进行实例化。
下面是创建 promise 的步骤:
var promise = new Promise(function(resolve, reject) {
// 异步处理
// 处理结束后、调用resolve 或 reject
});
Promise 构造函数包含一个参数和一个带有 resolve(解析)和 reject(拒绝)两个参数的回调。在回调中执行一些操作(例如异步),如果一切都正常,则调用 resolve,否则调用 reject。
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
let promise = new Promise((resolve, reject) => {
// 异步处理:使用定时器来模拟异步任务
setTimeout(() => {
let flag = false;
// 处理结束后、调用resolve(解析) 或 reject(拒绝)
if (flag) {
//当异步代码执行成功时,我们才会调用resolve(...), 当异步代码失败时就会调用reject(...)
resolve("成功!");
} else {
reject("失败!");
}
}, 200);
})
// 解析promise对象
promise.then(
// 如果为成功状态
(successMessage) => {
//successMessage的值是上面调用resolve(...)方法传入的值.
//successMessage参数不一定非要是字符串类型,这里只是举个例子
console.log("调用成功!");
console.log(successMessage);
},
// 如果为失败失败状态
(errorMessage)=>{
//errorMessage的值是上面调用reject(...)方法传入的值.
console.log("调用失败!");
console.log(errorMessage);
}
)
</script>
</body>
</html>
对于已经实例化过的 promise 对象可以调用 promise.then() 方法,传递 resolve 和 reject 方法作为回调。
promise.then() 是 promise 最为常用的方法。
promise.then(onFulfilled, onRejected)
promise简化了对error的处理,上面的代码我们也可以这样写:
promise.then(onFulfilled).catch(onRejected)
6、Promise调用Ajax
下面是一个用 Promise 对象实现的 Ajax 操作的例子。
resolve 方法和 reject 方法调用时,都带有参数。它们的参数会被传递给回调函数。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Promise调用原生的ajax请求</title>
</head>
<body>
<script>
// 创建Promise对象:解决异步事件嵌套依赖数据的问题
var promise = new Promise((resolve, reject) => {
// 1.创建xhr对象
let xhr = new XMLHttpRequest();
// 2.设置请求方式和URL地址
xhr.open("GET", "http://127.0.0.1:8024/world");
// 3.判断请求的次数和状态
xhr.onreadystatechange = function() {
// 当请求的次数不为4就返回
if (xhr.readyState != 4) return
// 如果请求成功
if (xhr.readyState == 4 && xhr.status == 200) {
// 将当前的服务器响应的数据以字符串的形式传给成功的函数
resolve(xhr.responseText);
} else {
reject("服务器异常!");
}
}
// 向服务器发起请求
xhr.send();
});
// 判断Promise对象的最终状态
promise.then(
// 成功
(success) => {
console.log(success);
},
// 失败
(error) => {
console.log(error);
}
)
</script>
</body>
</html>
7、Promise.then链式操作
链式操作,即返回一个promise对象,等待这个promise对象的状态,并将成功的结果,作为下一个ajax函数的参数,并再次调用函数,返回一个promise对象,这种行为操作就是链式操作,并有效的解决的开发中常见的ajax嵌套依赖数据的问题。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Promise链式操作</title>
</head>
<body>
<script>
// 可通用的原生ajax GET请求
function reusable(url) {
// 创建并返回Promise对象
return new Promise((resolve, reject) => {
// 1.创建xhr对象
let xhr = new XMLHttpRequest();
// 2.配置请求类型和请求URL地址
xhr.open("GET", "http://127.0.0.1:8024" + url);
// 3.判断请求的次数和状态
xhr.onreadystatechange = function() {
// 如果请求的状态不是最后一次,那么就返回
if (xhr.readyState != 4) return;
// 如果状态是最后一次,并且成功
if (xhr.readyState == 4 && xhr.status == 200) {
// 成功,并将参数传递
resolve(xhr.responseText);
} else {
// 失败,并将参数传递
reject("服务器响应失败!");
}
};
// 发起请求
xhr.send();
});
}
// 只要promise对象中的状态被确定,就立刻执行then函数,then函数就是等待promise对象的状态被确定,并且返回一个Promise实例对象
reusable("/getdata").then(
// 成功
(success) => {
console.log(success);
// 调用下一个ajxa,并返回Promise实例对象
return reusable("/addworld/success");
},
// 失败
(error) => {
console.log(error);
}
// 当上一个promise对象的状态被确定,再执行下一个then
).then((success) => {
// 成功
console.log(success);
// 失败
}).catch((error) => {
console.log(error);
// 成功与否都会执行
}).finally(() => {
console.log("结束");
})
</script>
</body>
</html>
8、then参数中的返回值
-
返回Promise实例对象
返回的该实例对象会调用下一个 then
-
返回普通值
返回的普通值会直接传递给下一个 then,通过 then 参数中函数的参数接收该值
9、Promise常用的API
A、实例方法
-
p.then() 得到异步任务的正确结果
-
p.catch() 获取异步任务异常信息
-
p.finally() 成功与否都会执行(尚且不是正式标准)
示例:
queryData().then(function(data){ console.log(data); }).catch(function(data){ console.log(data); }).finally(function(){ console.log(‘finished'); });
B、对象方法
-
Promise.all() 并发处理多个异步任务,所有任务都执行完成才能得到结果,执行结果由所有的p1,p2,p3共同决定,必须等所有的都完成,通过一个数组将所有的执行结果装起来给success
-
Promise.race() 并发处理多个异步任务,只要有一个任务完成就能得到结果,哪个先完成则success是谁
示例:
//调用三次这个方法,并将路由传递,最后返回三个不同的promise对象 let p1 = reusable('/getworld'); let p2 = reusable('/getdata'); let p3 = reusable('/addworld'); // 状态由三个一起来决定,只要有一个失败那么状态就是失败,否则全部成功状态才为成功,并返回一个数组 Promise.all([p1, p2, p3]).then((success) => { console.log(success); }).catch((error) => { console.log(error); }); // 谁先确定完状态,由谁来决定,成功与否都看第一个确定状态的对象,返回的就是哪个对象状态的结果 Promise.race([p2, p3, p1, ]).then((success) => { console.log(success); }).catch((error) => { console.log(error); })
三、Fetch
1、简介
fetch是基于Promise实现的一种HTTP数据请求的方式,是XMLHttpRequest的一种替代方案。fetch不是ajax的进一步封装,而是原生js。Fetch函数就是原生js,没有使用XMLHttpRequest对象。
2、语法结构
fetch(url,option).then(fn2)
.then(fn3)
...
.catch(fn)
示例:
let ajax = fetch("http://127.0.0.1:8024/updata", {
// ajax请求类型
method: "PUT",
//传输数据,必须为string
body: JSON.stringify({
val: 20
}),
//设置请求头
headers: {
// 传递json数据
'Content-Type': "application/json",
}
//这一步确定状态,并返回一个promise对象
}).then(
// 成功状态
(success) => {
// 返回数据类型为string
return success.text();
},
// 失败状态
(error) => {
console.log("服务器异常!");
}
// 这一步根据状态解析promise对象
).then((val) => {
// 打印返回的结果
console.log(val);
});
3、Fetch响应结果
响应数据格式
- text(): 将返回体处理成字符串类型
- json():返回结果和 JSON.parse(responseText)一样
4、Fetch 请求参数
(1)常用配置选项
- method(String): HTTP请求方法,默认为GET (GET、POST、PUT、DELETE)
- body(String): HTTP的请求参数
- headers(Object): HTTP的请求头,默认为{}
(2)GET请求方式的参数传递
-
查询参数
fetch(‘/abc?id=123‘).then(data=>{ return data.text(); }).then(ret=>{ // 注意这里得到的才是最终的数据 console.log(ret); });
-
params参数
fetch(‘/abc/123’,{ method: 'get' }).then(data=>{ return data.text(); }).then(ret=>{ // 注意这里得到的才是最终的数据 console.log(ret); });
(2)DELETE请求方式的参数传递
fetch('/abc/123' ,{
method: 'delete'
}).then(data=>{
return data.text();
}).then(ret=>{
// 注意这里得到的才是最终的数据
console.log(ret);
});
(3)POST请求方式的参数传递
-
查询参数
fetch('/books' ,{ method: 'post’, body: 'uname=lisi&pwd=123’, headers: { 'Content-Type': 'application/x-www-form-urlencoded', } }).then(data=>{ return data.text(); }).then(ret=>{ console.log(ret); });
-
json字符串参数
fetch('/books' ,{ method: 'post', body: JSON.stringify({ uname: 'lisi', age: 12 }) headers: { 'Content-Type': 'application/json', } }).then(data=>{ return data.text(); }).then(ret=>{ console.log(ret); });
(4)PUT请求方式的参数传递
fetch(‘/books/123' ,{
method: ‘put’,
body: JSON.stringify({
uname: 'lisi',
age: 12
})
headers: {
'Content-Type': 'application/json',
}
}).then(data=>{
return data.text();
}).then(ret=>{
console.log(ret);
});
5、Fetch 链式操作
// 使用fetch调用ajax,采用get方法,并返回一个Promise对象
let ajax = fetch("http://127.0.0.1:8024/getdata", {
method: "GET"
// 状态为成功
}).then(success => {
// 返回json类型的数据
return success.json()
// 状态为失败
}).catch(error => {
// 打印字符串
console.log("服务器异常!")
});
// 获取Promise对象状态为成功的值
ajax.then(function(val) {
// 成功以后调用下一个ajax
let ajax = fetch(`http://127.0.0.1:8024/addworld/${val.hellow}`, {
method: "GET"
//确定状态,并返回promise对象
}).then(success => {
return success.text()
// 状态为失败
}).catch(error => {
console.log("服务器异常!")
// 解析promise对象,并返回结果
}).then(val => {
console.log(val);
});
})
四、Axios
1、简介
Axios 是一个基于 promise 的 HTTP 库,简单的讲就是可以发送get、post请求。说到get、post,大家应该第一时间想到的就是jQuery吧,毕竟前几年Jquery比较火的时候,大家都在用他。但是由于Vue、React等框架的出现,Jquery也不是那么吃香了。也正是Vue、React等框架的出现,促使了Axios轻量级库的出现,因为Vue等,不需要操作Dom,所以不需要引入Jquery.js了。
2、特性
- 可以在浏览器中发送 XMLHttpRequests
- 可以在 node.js 发送 http 请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求数据和响应数据
- 能够取消请求
- 自动转换 JSON 数据
- 客户端支持保护安全免受 XSRF 攻击
3、使用场景
在特性里面已经有提到,浏览器发送请求,或者Node.js发送请求都可以用到Axios。像Vue、React、Node等项目就可以使用Axios,如果你的项目里面用了Jquery,此时就不需要多此一举了,jquery里面本身就可以发送请求。
4、基本用法
axios.type(URL:string,options:object).then(val=>{
// val为一个对象
// data属性名称是固定的,用于获取后台响应的数据
console.log(val.data)
})
注意:
axios.type(URL:string,options:object)
不带then是有返回值,返回值为promise
对象,因为axios支持支持 Promise- 如果使用then,那么就是解析promise对象,就可以获取服务器响应的结果
5、链式操作
// 调用ajax,并解析promise对象
axios.get("http://127.0.0.1:8024/getdata").then(val => {
// 将值作为参数进行洗一次的ajax请求
axios.delete("http://127.0.0.1:8024/delete", {
params: {
id: val.data
}
// 解析promise对象
}).then(val => {
// 拿到值
console.log(val.data);
})
})
6、参数传递
(1)GET传递参数
-
通过 URL 传递参数
-
查询参数
axios.get('/adata?id=123').then(ret=>{ console.log(ret.data) })
-
冒号参数
axios.get('/adata/123').then(ret=>{ console.log(ret.data) })
-
-
通过 params 选项传递参数
axios.get('/adata',{ params: { id: 123 } }).then(ret=>{ console.log(ret.data) })
(2)DELETE传递参数
-
通过 URL 传递参数
-
查询参数
axios.delete('/adata?id=123').then(ret=>{ console.log(ret.data) })
-
冒号参数
axios.delete('/adata/123').then(ret=>{ console.log(ret.data) })
-
-
通过 params 选项传递参数
axios.delete('/adata',{ params: { id: 123 } }).then(ret=>{ console.log(ret.data) })
(3)POST传递参数
-
通过选项传递参数(默认传递的是json格式的数据)
axios.post('/adata',{ uname: 'tom', pwd: 123 }).then(ret=>{ console.log(ret.data) })
-
通过 URLSearchParams 传递参数(application/x-www-form-urlencoded)
//创建一个URLSearchParams对象 const params = new URLSearchParams(); // 向实例对象中添加数据 params.append('param1', 'value1'); params.append('param2', 'value2'); // 调用ajax,并将URLSearchParams对象作为参数传递 axios.post('/api/test', params).then(ret=>{ console.log(ret.data) })
(4)PUT参数传递
通过选项传递参数(默认传递的是json格式的数据)
axios.put(‘/adata/123',{
uname: 'tom',
pwd: 123
}).then(ret=>{
console.log(ret.data)
})
7、响应结果
响应结果的主要属性:
- data : 实际响应回来的数据
- headers :响应头信息
- status :响应状态码
- statusText :响应状态信息
8、拦截器
(1)请求拦截器
功能:在请求发出之前设置一些信息
示例:
//添加一个请求拦截器
axios.interceptors.request.use(function(config){
//在请求发出之前进行一些信息设置
return config;
},function(err){
// 处理响应的错误信息
});
(2)响应拦截器
功能:在获取数据之前对数据进行一系列的加工处理
示例:
//添加一个响应拦截器
axios.interceptors.response.use(function(res){
//在这里对返回的数据进行处理
return res;
},function(err){
// 处理响应的错误信息
})
五、async/await
1、简述
- async/await是ES7引入的新语法,可以更加方便的进行异步操作
- async 关键字用于函数上,async函数的返回值是Promise实例对象
- await 关键字用于 async 函数当中,await用于等待异步操作完成,并得到结果,这个结果可以是一个Promise实例对象
2、示例
-
无参
// 声明该函数为异步的 async function books() { // 使用await关键字等待ajax请求的数据,并返回promise对象 const data = await axios.get("http://127.0.0.1:8024/getdata"); //并将promise对象返回 return data; } // 函数调用在这里:成功以后解析promise对象 books().then(val => { // 取值 console.log(val.data); })
-
有参
// 声明该函数为有参的异步 async function addworld(id) { // 将参数作为ajax请求的数据,并等待ajax请求结束,返回promise对象 const data = await axios.get("http://127.0.0.1:8024/addworld/" + id); // 返回promise对昂 return data; } // 函数调用在这里,将参数传递:成功以后解析promise addworld(123).then(val => { //取值 console.log(val.data); })
3、链式操作
// 将这个函数设置为异步函数
async function queryData() {
// 等待第一个异步事件执行并解析完成,拿到其值
const info = await axios.get("http://127.0.0.1:8024/getworld").then(val => {
// 将服务器响应的值返回
return val.data;
});
// 将返回的结果从第个异步ajax中作为参数传递,并返回promise对象
const ret = await axios.get(`http://127.0.0.1:8024/addworld/${info}`);
// 最后返回一个promise对象
return ret;
}
// 通过then解析promise对象拿到返回的值
queryData().then(val => {
// 获取其值
console.log(val.data);
})
六、总结
以上均为Ajax嵌套调用以及Ajax数据依赖问题提出的解决方案,由繁到简,由难到易,但都是围绕javascript中的Promise对象展开的,如果说你看不懂Promise对象,那么抱歉,你掌握不了其他的方法。所以学习Promise对象的必须的,它起到了一个承上启下基础作用。
如果在日常开发中,我们没有使用jQuery库而是选择使用Vue这种虚拟DOM的框架,我还是非常建议大家去使用axios来实现ajax,因为它是简易上手的,并且是轻量的。或者你再搭配上async和await那么将是非常不错的开发选择。
如果还要不懂的朋友请评论,或者私信博主。或者在本篇文章中有总结的不对的地方,还请各位博友指点。好,今天就跟大家一起学习到这里,下一期将给大家带来这几天总结的Vue框架学习的整套API文档和经典案例。