ES6的简单介绍(第三部分)
五 异步编程
5.5 回调地狱
回调地狱这个词听起来就非常的高大上,在接触Promise之前,必须要懂得什么是回调地狱,以及为什么会产生回调地狱?
先来看看概念:当一个回调函数嵌套一个回调函数的时候就会出现一个嵌套结构,当嵌套的多了就会出现回调地狱的情况。
根据前面我们可以得出一个结论:存在异步任务的代码,不能保证能按照顺序执行,那如果我们非要代码顺序执行呢?
举个例子:让四个回调函数打印骆宾王的《咏鹅》,咱们必须要这样操作,才能保证顺序正确:
setTimeout(function () {
setTimeout(function () {
setTimeout(function () {
setTimeout(function () {
console.log("红掌拨清波")
}, 1000)
console.log("白毛浮绿水")
}, 2000)
console.log("曲项向天歌")
}, 3000)
console.log("鹅鹅鹅")
}, 4000)
可以看到,代码中的回调函数套回调函数,居然套了4层,这种回调函数中嵌套回调函数的情况就叫做回调地狱。
总结一下,回调地狱就是为实现代码顺序执行而出现的一种操作,它会造成我们的代码可读性非常差,后期不好维护。
那该如何解决回调地狱呢?
两种方式
1. 使用ES6引入的promise
2. async/await
5.6 promise的应用
Promise是js中的一个原生对象(也可以理解为一个类或者一个构造函数),是一种异步编程的解决方案,可以替换掉传统的回调函数解决方案。
Promise是一种对异步操作的封装,表示一个操作现在可能还没有完成,但将来会完成,并提供了成功或失败时要执行的操作。
Promise: ES6引入的一个新的概念(可以理解为是一个类,或者是一个构造函数)
引入的目的主要是解决回调地狱的写法异步编程想要做到类似于同步变成的顺序执行,才会涉及到回调地狱
5.6.1 Promise的三种状态
Promise(承诺)的状态: 回调函数执行:从开始到结束。
pending: 等待状态: 承诺的这件事没有结果(结果只有两种情况,要么成功,要么失败),也可以理解为对象刚刚初始化完毕时的状态
fulfilled: 完成,成功状态: 承诺的这件事得到满意的结果。
rejected: 失败,拒绝状态: 承诺的这件事得到不满意的结果。比如类似ajax发送请求,结果收到了404,500这类的状态码
5.6.2 Promise的参数
Promise构造函数的参数,需要我们传入一个函数,我们需要处理的异步任务就是书写在该函数体内。传入的这个函数,要求必须要有两个参数,第一个参数是异步任务执行成功时的回调函数,第二个参数是异步任务执行失败时的回调函数,正常来说,参数名叫什么都无所谓,但是咱们尽量做到见名知意嘛,所以规范中命名为resolve(解决的含义),reject(拒绝)。异步任务执行成功时调用resolve函数返回结果,反之调用reject。
Promise构造函数的参数:
1.参数是一个回调函数。(参数1,参数2)=>{......}
2.回调函数里的第一个参数是一个函数,表示成功状态调用的函数,因此参数名可以做到见名知意,叫resolve(解决的含义)
3.回调函数里的第二个参数也是一个函数,表示失败、拒绝状态调用的函数,因此参数名可以做到见名知意,叫reject(拒绝的含义)
let p = new Promise( (resolve,reject)=>{
// 使用伪代码来模拟开启的一部编程的任务是否成功执行,或者是失败执行
/*
随机数为0~70: 表示任务执行成功
随机数为71~100: 表示任务执行失败
*/
let num1 = Math.round(Math.random()*100);
console.log(num1);
if (num1<=70){
resolve("任务1执行成功");
}else{
reject("任务1执行失败了");
}
});
5.6.3 常用API
- then()
Promise对象的then方法用来接收处理成功时响应的数据。
then(onfulfilled,onrejected): Promise对象调用该方法
1. 第一个参数onfulfilled: 用于给Promise的回调函数(参数)的第一个参数resolve赋值,赋的值是一个函数。
函数的样子: (data)=>{......} :任务执行的结果作为参数传递给data
2. 第二个参数onrejected: 用于给Promise的回调函数的第二个参数reject赋值,赋的值也是一个函数。
函数的样子: (reason)=>{......} :任务执行失败的结果作为参数传递给reason
作用:任务失败时,执行逻辑的封装。 可以代替catch。
应用场景:每个人物的失败都像单独处理,可以用第二个参数。
- catch()
catch方法用来接收处理失败时相应的数据。
catch(onrejected)方法: 任务失败时执行该方法,Promise对象调用该方法
1.参数只有一个onrejected:也是一个函数,用于给Promise的回调函数(参数)的第二个参数reject赋值,赋的值也是一个函数。
函数的样子: (reason)=>{......}
作用:所有的任务在失败时想要统一处理时,使用catch。Promise的链式调用:
p.then().then().then()...
注意: then()方法是Promise对象的,因此,想要链式调用then方法,那么前一个then必须返回一个新的Promise对象
演示代码:
let p = new Promise( (resolve,reject)=>{
// 使用伪代码来模拟开启的一部编程的任务是否成功执行,或者是失败执行
/*
随机数为0~70: 表示任务执行成功
随机数为71~100: 表示任务执行失败
*/
let num1 = Math.round(Math.random()*100);
console.log(num1);
if (num1<=70){
resolve("任务1执行成功");
}else{
reject("任务1执行失败了");
}
});
// let f1 = (data)=>{
// console.log("onfulfilled: "+data);
// }
// let f2 = (data)=>{
// console.log("onrejected: "+data);
// }
// p.then(f1,f2);
p.then((data)=>{
console.log("onfulfilled: "+data);
//第二个任务: 必须在任务1成功执行后,执行
return new Promise((resolve,reject)=>{
let num2 = Math.round(Math.random()*100)+100;
console.log(num2);
if (num2<=170){
resolve("任务2执行成功");
}else{
reject("任务2执行失败了");
}
})
},(reason)=>{
console.log("失败原因1: "+reason);
return new Promise((resolve,reject)=>{
let num2 = Math.round(Math.random()*100)+100;
console.log(num2);
if (num2<=170){
resolve("任务2执行成功");
}else{
reject("任务2执行失败了");
}
})
}).then((data)=>{
console.log("第二个任务: "+data);
},(reason)=>{
console.log("失败原因2: "+reason);
})
// .catch((reason)=>{
// console.log("失败原因: "+reason);
// })
// .catch((reason)=>{
// console.log("onrejected: "+reason);
// })
Promise完成异步的顺序执行代码
//
// let p =new Promise((resolve,reject)=>{
// resolve("鹅鹅鹅");
// })
//
// p.then((value)=>{
// console.log(value);
// return new Promise((resolve,reject)=>{
// resolve("曲项向天歌");
// })
// }).then((value)=>{
// console.log(value);
// return new Promise((resolve,reject)=>{
// resolve("白毛浮绿水");
// })
// }).then((value)=>{
// console.log(value);
// return new Promise((resolve,reject)=>{
// resolve("红掌拨清波");
// })
// }).then((value)=>{
// console.log(value);
// })
let p1=new Promise((resolve,reject)=>{
let num =Math.round(Math.random()*50);
if (num<=30){
resolve("鹅鹅鹅");
}else{
reject("锄禾日当午");
}
})
p1.then((data)=>{
console.log(data)
return new Promise((resolve,reject)=>{
let num2 =Math.round(Math.random()*50);
if (num2<=30){
resolve("曲项向天歌");
}else{
reject("汗滴禾下土");
}
})
},(value)=>{
console.log(value);
return new Promise((resolve,reject)=>{
let num2 =Math.round(Math.random()*50);
if (num2<=30){
resolve("曲项向天歌");
}else{
reject("汗滴禾下土");
}
})
}).then((data)=>{
console.log(data);
return new Promise((resolve,reject)=>{
let num2 =Math.round(Math.random()*50);
if (num2<=30){
resolve("白毛浮绿水");
}else{
reject("谁知盘中餐");
}
})
},(value)=>{
console.log(value);
return new Promise((resolve,reject)=>{
let num2 =Math.round(Math.random()*50);
if (num2<=30){
resolve("白毛浮绿水");
}else{
reject("谁知盘中餐");
}
})
}).then((data)=>{
console.log(data);
return new Promise((resolve,reject)=>{
let num2 =Math.round(Math.random()*50);
if (num2<=30){
resolve("红掌拨清波");
}else{
reject("粒粒皆辛苦");
}
})
},(value)=>{
console.log(value);
return new Promise((resolve,reject)=>{
let num2 =Math.round(Math.random()*50);
if (num2<=30){
resolve("红掌拨清波");
}else{
reject("粒粒皆辛苦");
}
})
}).then((data)=>{
console.log(data);
},(value)=>{
console.log(value);
})
5.7 Fetch
Fetch: 是一个基于Promise的API,可以发送HTTP请求
第一种:发送get请求
fetch(url).then((response)=>{...})
解析:1.url: 请求路径,get请求需要将请求参数拼接到url上。
2.第一个then里的匿名函数是用于处理请求成功后服务器响应的数据,形参resopnse是整个想要对象的封装,里面含有很多键值对的信息
比如: type,url,status,redirected,ok, 已经服务端绑定的数据。
3.如果想要获取服务端发送过来的数据,
需要 return 响应对象上的json() 实际上返回的是一个新的Promise
4.因此,如果想要继续处理数据,那么需要继续调用then方法。
5. 如果处理的是异常信息,可以使用catch
演示代码:
let btn1 = document.querySelector("#btn1");
// alert(btn1.nodeName);
btn1.addEventListener("click", function (){
fetch("checkLogin.lr?username=zhangsan").then((data)=>{
console.log(data);
return data.json();
}).then((data)=>{
console.log(data);
// console.log(data.error_msg);
document.querySelector("#s1").innerText = data.error_msg;
})
})
第二种:发送post请求
1.格式: fetch(url,{method:"post",body:"请求参数"})
2.解析: url: 请求路径
{}用于指定其他的参数
method:请求方式,默认为get
body:用于指定请求参数,使用对象类型的写法。还需要使用URLSearchParams类型进行包装
如果method制定了get请求,那么就不需要body属性,而是将请求参数拼接到url上
演示代码
let btn2 = document.querySelector("#btn2");
// alert(btn1.nodeName);
btn2.addEventListener("click", function (){
fetch("checkLogin.lr",
{method:"post",
headers:{'content-type':'application/x-www-form-urlencoded'},
body:"username=zhangsan"
}).then((data)=>{
console.log(data)
return data.json();
}).then((jsonStr)=>{
console.log(jsonStr.error_msg);
})
})
第三种: 发送post请求,并且发送json格式的数据
演示代码
let btn3 = document.querySelector("#btn3");
let obj = {username:"lisi"};
// alert(btn1.nodeName);
btn3.addEventListener("click", function (){
fetch("checkLogin.lr", {
method:"post",
// headers:{'content-type':'application/x-www-form-urlencoded'},
body:JSON.stringify(obj)
}).then((data)=>{
console.log(data)
return data.json();
}).then((jsonStr)=>{
console.log(jsonStr.error_msg);
})
})
5.8 Async/Await
Async/await是建立在Promises之上的语法糖,使得异步代码的编写和理解更接近传统的同步代码风格。(使用同步的方式编写异步代码)
5.8.1 Async关键字
声明函数时,在前面添加Async关键字,表示该函数为一个异步任务,不会阻塞后面函数的执行。
async function fn(){
return '任务1';
}
console.log(fn()); // Promise{fulfilled: "任务1"}
打印可以看到async函数返回数据时自动封装为一个Promise对象。
和Promise对象一样,处理异步任务时也可以按照成功和失败来返回不同的数据,处理成功时用then方法来接收,失败时用catch方法来接收数据:
虽然Promise可以解决回调地狱的问题,但是Promise变成充斥着大量的then函数。
因此可以使用Async/Await来简化Promise的异步编程。
Async/Await是建立在Promise之上的一个语法糖,可以使用同步代码的风格实现异步编程。Async: 异步的意思, 用法简单, 只需要在定义的函数前,添加async关键字即可。
1. async声明的函数,会隐式返回一个Promise对象。
2. async声明的函数,还是使用then或者catch来调用
3. 函数中如果正确使用return关键字进行了数据的返回,那么Promise的状态就是fulfilled。
如果函数中的代码抛出了异常,那么Promise的状态就是rejected。
4. async修改的函数,本质上是Promise,调用时还是异步代码,并不会阻塞后续的代码运行。
5. 在普通函数前添加async关键字,等价于在普通函数中return 一个Promise对象
async function f1(){
// console.log("任务一");
try{
return "任务一";
}catch (e){
throw new Error("任务一出错了")
}
}
// function f1(){
// return new Promise((resolve,reject)=>{
// try{
// //......省略大量代码
// resolve("任务一")
// }catch (e){
// reject("任务一出错了")
// }finally {
// //释放资源
// }
//
// })
// }
f1().then((value)=>{
console.log(value);
},(reason)=>{
console.log(reason);
})
console.log("---我是主线程代码---")
5.8.2 Await关键字
await关键字只能在使用async定义的函数中使用
await后面可以直接跟一个 Promise实例对象(可以跟任何表达式,更多的是跟一个返回Promise对象的表达式)
await函数不能单独使用, await等待的意思,可以等待任何表达式,不过最常用的是等待一个Promise对象。
await可以直接拿到Promise中resolve中的数据。
await后续的代码什么时候才执行? 等到结果后,才会执行。 也就是await会阻塞后面的代码
测试代码:
// 两秒后获得一个随机数
function getRandomNumber(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(Math.round(Math.random()*3));
},2000)
})
}
// async function getRandomNumber(){
// setTimeout(()=>{
// return Math.round(Math.random()*3);
// },2000)
// }
async function test(){
// 2秒后,获取一个随机数
let r1 = await getRandomNumber(); //等2秒,可以获取随机数,给变量r1
let r2 = await getRandomNumber(); //继续等2秒,获取第二个随机数,给变量r2
let r3 = await getRandomNumber(); // 继续等2秒,获取第三个随机数,给变量r3
console.log(r1,r2,r3);
}
test();
console.log("---我是6秒前执行的,await关键字并不影响主线程中的代码执行。---")
5.9 Axios
5.9.1 Axios简介
Axios(中文谐音:爱克丝赛欧斯)
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
官方网站:http://www.axios-js.com
源码:https://gitee.com/charlinchenlin/store-pos
特性:
1、从浏览器中创建 XMLHttpRequests
2、从 node.js 创建 http 请求
3、支持 Promise API
4、拦截请求和响应
5、转换请求数据和响应数据
6、取消请求
7、自动转换 JSON 数据
8、客户端支持防御 XSRF
5.9.2 安装方式
使用 npm:
$ npm install axios
使用 bower:
$ bower install axios
使用CDN
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
5.9.3 案例演示
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>axios</title>
</head>
<body>
<button id="btn1">axios发生get请求--方法</button>
<button id="btn2">axios发生post请求--方法</button>
<button id="btn3">axios发生get请求</button>
<button id="btn4">axios发生post请求</button>
<br><span id="s1" style="color: red"></span>
</body>
<script src="js/axios.js"></script>
<script>
let btn1 = document.querySelector("#btn1");
let btn2 = document.querySelector("#btn2");
let btn3 = document.querySelector("#btn3");
let btn4 = document.querySelector("#btn4");
/*
axios.get((url,{}))
解析: url: 请求资源路径
{}:用于指定其他参数的,比如请求参数,注意请求参数的key是固定写法,必须叫params
params的值,也只能是对象形式。
*/
btn1.addEventListener("click",()=>{
//模拟从文本框拿到数据后做了一个对象的封装
let loginInfo = {"username":'zhangsan',password:123456}
axios.get("checkLogin.lr",{params:loginInfo}).then((response)=>{
// 因为response这个对象上有一个叫data的属性,该属性用于存储服务端发送过来的数据,因此直接使用data属性即可
// console.log(response.data)
let obj = response.data;
document.querySelector("#s1").innerText=obj.error_msg;
})
})
/**
* 后端Servlet只能从getReader()里读取数据,getParameter(...)不行
*/
// btn2.addEventListener("click",()=>{
// //模拟从文本框拿到数据后做了一个对象的封装
// let loginInfo = {"username":'zhangsan',password:123456};
// axios.post("checkLogin.lr",loginInfo).then((response)=>{
// // 因为response这个对象上有一个叫data的属性,该属性用于存储服务端发送过来的数据,因此直接使用data属性即可
// console.log(response.data)
// let obj = response.data;
// document.querySelector("#s1").innerText=obj.error_msg;
// })
// })
btn2.addEventListener("click",()=>{
//模拟从文本框拿到数据后做了一个对象的封装
let loginInfo = {"username":'zhangsan',password:123456};
axios.post("checkLogin.lr","username=zhangsan&&password=123456").then((response)=>{
// 因为response这个对象上有一个叫data的属性,该属性用于存储服务端发送过来的数据,因此直接使用data属性即可
console.log(response.data)
let obj = response.data;
document.querySelector("#s1").innerText=obj.error_msg;
})
})
/**
* axios({
* url: 请求路径,
* method: 请求方式,默认为get,
* params/data: get请求时,使用params传请求参数,post请求时,必须使用data传请求参数
* headers: 可选,post请求时,必须设置
* })
*/
btn3.addEventListener("click",()=>{
//模拟从文本框拿到数据后做了一个对象的封装
let loginInfo = {"username":'zhangsan',password:123456};
axios({
url:"checkLogin.lr",
method:"get",
params:loginInfo
}).then((response)=>{
// 因为response这个对象上有一个叫data的属性,该属性用于存储服务端发送过来的数据,因此直接使用data属性即可
console.log(response.data)
let obj = response.data;
document.querySelector("#s1").innerText=obj.error_msg;
})
})
btn4.addEventListener("click",()=>{
//模拟从文本框拿到数据后做了一个对象的封装
let loginInfo = {"username":'zhangsan',password:123456};
axios({
url:"checkLogin.lr",
method:"post",
headers:{"content-type":"application/x-www-form-urlencoded"},
data:loginInfo
}).then((response)=>{
// 因为response这个对象上有一个叫data的属性,该属性用于存储服务端发送过来的数据,因此直接使用data属性即可
console.log(response.data)
let obj = response.data;
document.querySelector("#s1").innerText=obj.error_msg;
}).catch((error)=>{
console.log(error);
})
})
</script>
</html>