课程地址:【ES6 Promise的用法,ES7 async/await异步处理同步化,异步处理进化史】 https://www.bilibili.com/video/BV1XW4y1v7Md/?share_source=copy_web&vd_source=b1cb921b73fe3808550eaf2224d1c155
图文地址:https://www.bilibili.com/read/cv18799030/?spm_id_from=333.999.0.0
目录
Promise介绍
1.1 异步请求的痛点-回调地狱
1.2 用callback回调函数的方式封装返回值
1.2.2 getList()
1.2.3 getComment()
1.2.4 完整代码
1.3 Promise的三种状态和then的链式调用法改造
1.3.1 实例化后的原型
1.3.2 Promise的3种状态
1 pending
2 resolve
3 reject
1.3.3 Promise处理异步
1 resolve和reject方法
2 then方法
3 catch方法
1.3.4 对函数进行改造
1 获取导航列表
2 获取新闻列表
3 获取当前新闻的评论
1.3.5 总结
1.4 Promise.all封装多个对象统一处理方案
1.4.1 catch方法
1.4.2 封装多个对象
1.5 async await异步处理同步化对代码重构
1.5.1 函数再改造
1.5.2 总结
Promise介绍
这门课主要讲解 解决异步请求 的方案,讲解 异步请求 的进化史。
同步和异步的问题,其实就是编程的思路。
JavaScript是单线程的程序,代码是顺序执行的,是一行一行的执行的。对于简单的代码,单线程是可以完全解决问题的。但是如果涉及网络请求,网络请求是根据网速和服务器带宽决定的。有的时候网络请求比较慢,出现 超时情况。如果下一行代码依赖上一行,会导致网页失去响应。
因为上述问题的存在,所以出现异步。异步与同步相反,代码执行不是按照顺序进行的。遇到网络请求,发送请求,等请求进行响应,然后再回来执行,这个过程的代码继续往下执行,打破了顺序的关系。
理论上讲,异步执行的效率更高一些。异步请求是前端常用的方式。
1.1 异步请求的痛点-回调地狱
同步的问题在于 效率不高,异步也存在问题——回调地狱。
回调地狱:异步调用获取到结果后,为下一个异步函数提供参数,所以就会一层一层出现回调里面嵌入回调,导致层次很深,代码维护起来特别的复杂。
出现回调地狱问题,ES6有Promise方法来解决回调地狱问题。
创建一个uniapp项目来体会下。
(创建一个uniapp项目可以见【b站咸虾米】chapter1&2_uniapp介绍与uniapp基础_新课uniapp零基础入门到项目打包(微信小程序/H5/vue/安卓apk)全掌握_uniapp 打包微信app 配置-CSDN博客)
<script>
export default {
data() {
return {
title: 'Hello'
}
},
onLoad() {
this.getData();
},
methods: {
getData(){
//获取分类列表id
uni.request({
url:"https://ku.qingnian8.com/dataApi/news/navlist.php",
success:res=>{
let id=res.data[0].id;
console.log(res);
// 根据分类id获取该分类下的所有文章
uni.request({
url:"https://ku.qingnian8.com/dataApi/news/newslist.php",
data:{
cid:id
},
success:res2=>{
//获取到一篇文章的id,根据文章id找到该文章下的评论
let id=res2.data[0].id;
console.log(res2);
uni.request({
url:"https://ku.qingnian8.com/dataApi/news/comment.php",
data:{
aid:id
},
success:res3=>{
//找到该文章下所有的评论
console.log(res3)
}
})
}
})
}
})
}
}
}
</script>
返回的结果
res3返回一个空数组。
以上就是原来常用的方式,这就是当时异步请求的痛点。
因此引入了Promise,来解决上述的问题。
1.2 用callback回调函数的方式封装返回值
1.2.1 getNav()
体会callback的作用,就是将getNav请求到的结果res作为参数返回到callback中进行处理。
onLoad() {
this.getNav(res=>{
console.log(res);
let id = res.data[0].id;
});
},
methods: {
// 获取导航列表
// callback是函数getNav的一个回调函数
getNav(callback) {
uni.request({
url: 'https://ku.qingnian8.com/dataApi/news/navlist.php',
success: res=> {
// console.log(res);
callback(res); // 将res返回到callback中,因此
//在调用getNav时,callback会作为回调函数去执行
}
})
},
请求返回的结果
1.2.2 getList()
在getNav方法的基础上,使用getList方法获取新闻列表。
getList方法的定义,也是有一个回调函数callback。
回调函数可以随便起名,callback是常用的名字。
// 获取新闻列表
getList(id, callback) {
uni.request({
url: 'https://ku.qingnian8.com/dataApi/news/newslist.php',
data: {
cid: id
},
success: res=>{
// console.log(res);
callback(res);// 将res作为实参返回给callback中
}
})
},
在getNav方法中调用getList方法。
onLoad() {
this.getNav(res=>{
console.log(res);
let id = res.data[0].id;
this.getList(id, res => {
console.log(res);
});
});
},
执行结果。
1.2.3 getComment()
getComment()定义。
// 获取当前新闻的评论
getComment(id, callback){
uni.request({
url: 'https://ku.qingnian8.com/dataApi/news/comment.php',
data: {
aid: id
},
success: res => {
callback(res);
}
})
},
调用getComment方法
onLoad() {
this.getNav(res=>{
console.log(res);
let id = res.data[0].id;
this.getList(id, res => {
console.log(res);
let id = res.data[0].id;
this.getComment(id, res=>{
console.log(res);
})
});
});
},
效果
有点问题,没有返回值(可能是接口有问题,不管了)
1.2.4 完整代码
可以发现,使用callback回调函数将 三层网络请求 独立出来了,可读性要比1.1的实现好一些。
传统型的回调函数作回调处理。
在es6之前,最常用这种方式。其实这种方式还是 回调地狱,拿到上一个结果后,再处理另一个请求,这个请求拿到结果后,再处理下一个请求。
之前1.1节内容的所有请求都写在一起,可维护性差。这里将1.1节的函数封装出去,在调用的时候看上去更加简单。
这是异步处理的1.0版本。
// 传统型的回调函数作回调处理
this.getNav(res=>{
let id = res.data[0].id;
this.getList(id, res => {
let id = res.data[0].id;
this.getComment(id, res=>{
console.log(res);
})
});
});
},
以上就是回调函数作异步处理。
完整代码如下:
<script>
export default {
data() {
return {
title: 'Hello'
}
},
onLoad() {
this.getNav(res=>{
console.log(res);
let id = res.data[0].id;
this.getList(id, res => {
console.log(res);
let id = res.data[0].id;
this.getComment(id, res=>{
console.log(res);
})
});
});
},
methods: {
// 获取导航列表
// callback是函数getNav的一个回调函数
getNav(callback) {
uni.request({
url: 'https://ku.qingnian8.com/dataApi/news/navlist.php',
success: res=> {
// console.log(res);
callback(res); // 将res返回到callback中,因此在调用getNav时,callback会作为回调函数去执行
}
})
},
// 获取新闻列表
getList(id, callback) {
uni.request({
url: 'https://ku.qingnian8.com/dataApi/news/newslist.php',
data: {
cid: id
},
success: res=>{
// console.log(res);
callback(res);// 将res作为实参返回给callback中
}
})
},
// 获取当前新闻的评论
getComment(id, callback){
uni.request({
url: 'https://ku.qingnian8.com/dataApi/news/comment.php',
data: {
aid: id
},
success: res => {
callback(res);
}
})
}
}
}
</script>
1.3 Promise的三种状态和then的链式调用法改造
下面来看es6以后,promise的实现方式。是现在比较主流的一种形式。
promise就是一种解决异步的处理方式,本质是一个构造函数。
可以用来实例化一个对象,对象身上有resolve、reject和all,原型上有 then 和 catch方法。
promise对象有3种状态,pending(进行中)、resolved/fullfilled(成功)、rejected(失败)。
1.3.1 实例化后的原型
执行console.dir(Promise) Promise是个构造函数。
结果
Promise实例化对象的原型
1.3.2 Promise的3种状态
1 pending
1. pending。它的意思是 "待定的,将发生的",相当于是一个初始状态。创建Promise对象时,且没有调用resolve或者是reject方法,相当于是初始状态。这个初始状态会随着你调用resolve,或者是reject函数而切换到另一种状态。
举例。
// 实例化一个对象
// resolve和reject可以用任意合法变量名称(比如a、b)代替,但是最好不要这样
let p = new Promise((resolve, reject)=>{
});
console.log(p);
此时的p,在pending状态,表示什么也没发生。
2 resolve
2. resolved。表示解决了,就是说这个承诺实现了。 要实现从pending到resolved的转变,需要在 创建Promise对象时,在函数体中调用了resolve方法。
举例
// 实例化一个对象
// resolve和reject可以用任意合法变量名称(比如a、b)代替,但是最好不要这样
let p = new Promise((resolve, reject)=>{
resolve(1);
});
console.log(p);
结果
3 reject
3. rejected。拒绝,失败。表示这个承诺没有做到,失败了。要实现从pending到rejected的转换,只需要在创建Promise对象时,调用reject函数。
// 实例化一个对象
// resolve和reject可以用任意合法变量名称(比如a、b)代替,但是最好不要这样
let p = new Promise((resolve, reject)=>{
reject(1);
});
console.log(p);
1.3.3 Promise处理异步
Promise用来处理异步。
之前用回调函数callback,但是其实是不合理的。如果返回两个参数,难道要写两个函数吗?因此是不合理的。
1 resolve和reject方法
这里回调函数有两个,请求成功,执行resolve回调函数,返回res,请求失败,执行reject回调函数,返回err。
// 实例化一个对象
// resolve和reject可以用任意合法变量名称(比如a、b)代替,但是最好不要这样
let p = new Promise((resolve, reject)=>{
uni.request({
url:'https://ku.qingnian8.com/dataApi/news/navlist.php',
success: res=>{
resolve(res);
},
fail: err=> { // 失败,请求无效的时候会走这里
reject(err);
}
})
});
console.log(p);
请求成功,打印出来的p如下
请求失败(比如地址url不存在),打印出来的p如下
以上,请求成功,执行resolve回调函数,返回res,请求失败,执行reject回调函数,返回err。
请求成功和失败都进行了返回,成功返回res,失败返回err,怎么去用呢?
使用原型的then方法。
2 then方法
这里的p就是返回值。
p本质是个Promise对象。那么p的值在哪呢?
直接写个p.then()。p可以接收resolve和reject的返回值,请求成功就执行then方法。
res的值打印出来如下
3 catch方法
那么请求失败的时候,使用catch方法。
如果请求失败了,那么resolve就不返回res了,而是reject返回错误信息。然后p接收reject的返回值,执行catch方法。
结果,返回错误信息,并打印。
1.3.4 对函数进行改造
1 获取导航列表
methods: {
// 获取导航列表,返回一个Promise对象
getNav() {
return new Promise((resolve, reject)=>{
uni.request({
url: 'https://ku.qingnian8.com/dataApi/news/navlist.php',
success: res=> {
resolve(res);
},
fail: err=> {
reject(err);
}
})
})
},
在onLoad里调用getNav方法。
请求成功,Promise对象(this.getNav(返回的对象)接收resolve的返回值,执行then方法处理返回值res。
结果
成功返回数据
2 获取新闻列表
// 获取新闻列表
getList(id) {
return new Promise ((resolve, reject)=>{
uni.request({
url: 'https://ku.qingnian8.com/dataApi/news/newslist.php',
data: {
cid: id
},
success: res=>{
resolve(res);
},
fail: err=>{
reject(err);
}
})
})
},
调用
此时将18-26行的整个then方法看做一个整体,这是一个函数,函数的返回值又是一个Promise对象,则可以在这个Promise对象后面直接写then方法。这就是then的链式调用。
this.getNav().then(res=>{
console.log(res);
let id = res.data[0].id;
// this.getList(id);
// this.getList(id) 是一个Promise对象,我们要想用他的then方法,怎么办呢?
// 直接在此处return这个Promise对象
return this.getList(id);
// 这里直接returnPromise对象,相当于把这个Promise返回出去了。
}).then(res=>{
console.log(res);
})
结果
执行成功。
提示:getList()返回一个Promise对象。我们只是将getNav()的参数传给getList(),并没有对这个Promise对象做处理。
接着就对他进行return了,return之后,以下红框内的就是一个Promise对象,如果处于fulfilled状态,那么是有返回值的,返回值就包括在then方法里。
这里的res就是getList()的返回值。
3 获取当前新闻的评论
// 获取当前新闻的评论
getComment(id){
return new Promise((resolve, reject)=>{
uni.request({
url: 'https://ku.qingnian8.com/dataApi/news/comment.php',
data: {
aid: id
},
success: res => {
resolve(res);
},
fail:err=>{
reject(err);
}
})
})
},
然后调用上述方法。
onLoad() {
this.getNav().then(res=>{
console.log(res);
let id = res.data[0].id;
return this.getList(id);
}).then(res=>{
console.log(res);
let id = res.data[0].id;
return this.getComment(id);
}).then(res=>{
console.log(res);
})
},
结果
接口有问题,还是返回这个
1.3.5 总结
使用回调函数封装,实际还是一层层嵌套。
现在不属于嵌套了,这样代码清晰明了。
将层层嵌改为链式调用。以前的都是一环套一环,俄罗斯套娃,现在是用链式关联。这样阅读代码可以一块一块看,比嵌套的方式更加容易维护。
这是异步请求处理的2.0版本。(回调函数callback是1.0版本)。2.0版本后是3.0版本es7的方式。
这种方式非常好读,比回调地狱易用性很高。
完整代码
<script>
export default {
data() {
return {
title: 'Hello'
}
},
async onLoad() {
this.getNav().then(res=>{
let id = res.data[0].id;
return this.getList(id);
})
.then(res=>{
let id = res.data[0].id;
return this.getComment(id);
})
.then(res=>{
console.log(res);
});
},
methods: {
// 获取导航列表,返回一个Promise对象
getNav() {
return new Promise((resolve, reject)=>{
uni.request({
url: 'https://ku.qingnian8.com/dataApi/news/navlist.php',
success: res=> {
resolve(res);
},
fail: err=> {
reject(err);
}
})
})
},
// 获取新闻列表
getList(id) {
return new Promise ((resolve, reject)=>{
uni.request({
url: 'https://ku.qingnian8.com/dataApi/news/newslist.php',
data: {
cid: id
},
success: res=>{
resolve(res);
},
fail: err=>{
reject(err);
}
})
})
},
// 获取当前新闻的评论
getComment(id){
return new Promise((resolve, reject)=>{
uni.request({
url: 'https://ku.qingnian8.com/dataApi/news/comment.php',
data: {
aid: id
},
success: res => {
resolve(res);
},
fail:err=>{
reject(err);
}
})
})
},
getData(){
//获取分类列表id
uni.request({
url:"https://ku.qingnian8.com/dataApi/news/navlist.php",
success:res=>{
let id=res.data[0].id;
console.log(res);
// 根据分类id获取该分类下的所有文章
uni.request({
url:"https://ku.qingnian8.com/dataApi/news/newslist.php",
data:{
cid:id
},
success:res2=>{
//获取到一篇文章的id,根据文章id找到该文章下的评论
let id=res2.data[1].id;
console.log(res2);
uni.request({
url:"https://ku.qingnian8.com/dataApi/news/comment.php",
data:{
aid:id
},
success:res3=>{
//找到该文章下所有的评论
console.log(res3)
}
})
}
})
}
})
}
}
}
</script>
1.4 Promise.all封装多个对象统一处理方案
(还是异步请求2.0版本)
1.4.1 catch方法
catch只会存在一个。
代码
this.getNav().then(res=>{
let id = res.data[0].id;
return this.getList(id);
})
.then(res=>{
let id = res.data[0].id;
return this.getComment(id);
})
.then(res=>{
console.log(res);
}).catch(err=>{
console.log(err);
})
1.4.2 封装多个对象
以上的案例多个请求之间存在依赖关系。
本节介绍同步执行的多个请求的案例。
给之前获取新闻列表和评论的请求写死入参就可以将上述的有前后关系的请求改为同步请求了。
this.getNav().then(res=>{
console.log("导航");
console.log(res);
});
this.getList(51).then(res=>{
console.log("列表");
console.log(res);
});
this.getComment(251).then(res=>{
console.log("评论");
console.log(res);
});
执行结果
观察发现,顺序不是导航->列表->评论。顺序没有按照同步的方式进行,因为这都是异步请求,且没有依赖关系。谁的请求速度快,谁得到了响应,谁就先执行。
可以看出,列表最先执行。当然再次刷新,顺序可能会改变,因为这跟网络请求有关。
场景:对于一个页面,不同请求数据的请求都在执行,需要等到所有数据都返回后再渲染页面。
实现如下。
let p1 = this.getNav();
let p2 = this.getList(51);
let p3 = this.getComment(251);
Promise.all([p1, p2, p3]).then(res=>{
console.log(res);
})
结果
按照顺序执行的结果。
all方法有助于页面加载时做一些操作。
1.5 async await异步处理同步化对代码重构
axios,很多地方用了then方法,说明它内部已经封装了Promise了,使用的时候直接调用即可。
uniapp,基本网络请求,大多数不支持then,都是在success回调的。
uniCloud,已经封装了Promise了。有两种方式:Promise方式和callback方式。
然后来看ES7,async和await。异步处理同步化。
这两个命令是成对出现的,如果使用await没有在函数中使用async命令,那就会报错。如果直接使用async没有使用await不会报错,只是返回的函数是个promise,可以,但是没有意义,所以这两个一起使用才会发挥出它们本身重要的作用。
现在async和await满天飞,一些云处理,异步同步化,基本都是用await、async。
1.5.1 函数再改造
函数返回值必须是Promise对象,才能用await进行等待;如果不是Promise对象,根本无法使用await。
let id, res;
// 将函数的返回值定义为res变量
// 函数返回值必须是Promise对象,才能用await进行等待
// 如果不是Promise对象,根本无法使用await
res = await this.getNav();
console.log(res);
id = res.data[0].id;
res = await this.getList(id);
console.log(res);
id = res.data[2].id;
res = await this.getComment(id);
console.log(res);
结果
完整代码
<script>
export default {
data() {
return {
title: 'Hello'
}
},
async onLoad() {
let id, res;
// 将函数的返回值定义为res变量
// 函数返回值必须是Promise对象,才能用await进行等待
// 如果不是Promise对象,根本无法使用await
res = await this.getNav();
console.log(res);
id = res.data[0].id;
res = await this.getList(id);
console.log(res);
id = res.data[2].id;
res = await this.getComment(id);
console.log(res);
},
methods: {
// 获取导航列表,返回一个Promise对象
getNav() {
return new Promise((resolve, reject)=>{
uni.request({
url: 'https://ku.qingnian8.com/dataApi/news/navlist.php',
success: res=> {
resolve(res);
},
fail: err=> {
reject(err);
}
})
})
},
// 获取新闻列表
getList(id) {
return new Promise ((resolve, reject)=>{
uni.request({
url: 'https://ku.qingnian8.com/dataApi/news/newslist.php',
data: {
cid: id
},
success: res=>{
resolve(res);
},
fail: err=>{
reject(err);
}
})
})
},
// 获取当前新闻的评论
getComment(id){
return new Promise((resolve, reject)=>{
uni.request({
url: 'https://ku.qingnian8.com/dataApi/news/comment.php',
data: {
aid: id
},
success: res => {
resolve(res);
},
fail:err=>{
reject(err);
}
})
})
},
getData(){
//获取分类列表id
uni.request({
url:"https://ku.qingnian8.com/dataApi/news/navlist.php",
success:res=>{
let id=res.data[0].id;
console.log(res);
// 根据分类id获取该分类下的所有文章
uni.request({
url:"https://ku.qingnian8.com/dataApi/news/newslist.php",
data:{
cid:id
},
success:res2=>{
//获取到一篇文章的id,根据文章id找到该文章下的评论
let id=res2.data[1].id;
console.log(res2);
uni.request({
url:"https://ku.qingnian8.com/dataApi/news/comment.php",
data:{
aid:id
},
success:res3=>{
//找到该文章下所有的评论
console.log(res3)
}
})
}
})
}
})
}
}
}
</script>
1.6 总结
① 层层嵌套;
② 1.0版本,callback回调函数处理;
③ 2.0版本,返回Promise对象,使用then链式调用;调用方便++;
④ 3.0版本,ES7引入的async和await,让异步请求进行等待,然后将返回值赋给变量,进而去请求下一个内容。
目前,对于互相依赖型的请求,第③种使用的频率比较高。
⑤ 未来会有更好的方式...