这段时间一直在重构项目,遇见很多请求“高并发”,因为涉及到多个请求又或者多个连续请求。之所以给高并发带上引号,因为面对大量请求的时候,我们需要调整好姿势,怎样去好好的去写异步回调,弄清各个请求的顺序,稍微不注意可能就掉坑了,可能调试半天看着vue-devtool控制台打印的自己以为的“完美的数据”,但是页面各种显示不听话。哦,那说明你掉坑了!
理清好执行的先后顺序,其次再写回调的时候我们就要选好正确的方法和正确的姿势,这样才不会造成将来你写的代码你认不清的尴尬,同时感觉代码一目了然!
同时考虑到异步回调我们需要理解一些知识:
js的运行机制:
在代码运行时会形成任务队列,分为同步任务队列和异步任务对列,同步队列优先加载,异步任务队列会形成队列任务池,定时器不会一下加载到异步任务,而是在设定的时间后加载到异步任务,即使设置为0,浏览器也有它的响应时间,以前是10ms.现在是4ms.异步任务包括dom 事件,定时器,promise
首先作为异步回调的功能和作用不去作过多解释,对于js这种单线程异步回调是性能优化的一些点。
异步回调我觉得主要有两方面作用:
- 不阻碍程序运行,将一些延时较久的函数异步执行,不妨碍正常同步运行的代码;
- 一个函数必须在某个函数执行完成后才能运行,比如说需要用函数执行完后的某些数据;
首先写异步回调的姿势大概有这样几种
- 直接函数套函数(通俗的讲)
就像这样
function fn(callback) {
setTimeout(() => {
callback()
}, 1000);
}
function f1() {
console.log('f1')
}
fn(f1)
// fn
// f1
这样我们感觉还行,还不错,还能接受,那如果有另外一个f2函数,f1像fn那样,在多个f3:
function fn(callback) {
setTimeout(() => {
callback(f2)
}, 1000);
console.log('fn')
}
function f1(callback) {
setTimeout(() => {
callback(f3)
}, 1000);
console.log('f1')
}
function f2(callback) {
setTimeout(() => {
callback(f3)
}, 1000);
console.log('f2')
}
function f3() {
console.log('f3');
}
fn(f1)
// fn
// f1
// f2
//f3
这样看着代码也没这么乱,但是感觉把自己调懵了,如果想看出它的一些过程,我们将函数改写一下
function fn(callback) {
setTimeout(() => {
callback((callback) => {
setTimeout(() => {
callback()
}, 1000);
console.log('f2')
})
}, 1000);
console.log('fn')
}
fn((callback) => {
setTimeout(() => {
callback((callback) => {
console.log('f3')
})
}, 1000);
console.log('f1')
})
// fn
// f1
// f2
// f3
如果连起来看,分清之前的f1,f2,f3函数很困难吧,这还只是三个回调函数,有时候不仅仅这些吧,如果再来两个,咋样???
所以传统方法晦涩难懂。。。。
- 第二种方法看起来可能就比较爽了------Promise
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件,更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理
前两句话是摘自阮一峰老师的《es6入门》
我理解的Promise其实就是给你封装好的一个异步对象,本身有resolve和reject参数,当然也是函数,其实这里叫函数也不太好,我们不如说叫“自定义钩子”,当然这个还不像我们常说的钩子函数那样。Promise对象外部暴露的了两个分别对应的是成功之后的then函数,另一个是reject函数。说白了这两个函数是由用来设计resolve和reject函数的。Promise只负责将你写的函数在内部调用,同时将他的返回值有两个(这里说的两个是两种结果)传递出来,成功之后自然就是resolve(data),失败是reject(data),当然我们写resolve的代码块其实就是Promise实例执行完后对应的then函数的执行,当然data就是then函数回调的参数,当然reject和catch()也是一样的道理。其实看着这么像函数传参的过程,也这么像“依赖注入”的赶脚。
不说别的了,直接将上面函数改写一下:
function fn() {
console.log('fn');
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, 1000);
})
}
function f1() {
console.log('f1')
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, 1000);
})
}
function f2() {
console.log('f2')
return new Promise(resolve2 => {
setTimeout(() => {
resolve2()
}, 1000);
})
}
function f3() {
console.log('f3');
}
fn().then(() => {
f1().then(() => {
f2().then(() => {
f3()
})
})
})
// fn
// f1
// f2
// f3
等到讲aync和await时再好好讲讲Promise,今天的项目有一个特点就被埋坑了
有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易,看着上面的代码是不是清爽多了,但是感觉函数一多结构看着似乎也不那么友好,同时Promise也有弊端就是获取错误信息的时候,这些就不赘述了,网上应该有很多介绍的,今天的主角是async 和 await
- async 和 await
这两个方法其实就是Generator 函数的语法糖。详情请见阮一峰的es6
先说说背景,今天做一个黑名单的需求:
拉取软件列表的时候,首先我需要对list中存在的黑名单来个请求黑名单的请求,同时请求完成后我们需要将黑名单list存储在vue的data对象里,然后再通过对list进行过滤,对是黑名单的item进行软件图标的置灰操作,同时在不同的分类我们还需要进行判断是否进行过黑名单的操作,若果进行过就可以对本分组进行锁定和解锁操作,同时后续操作我们还需要拉取一个已经锁定分组的集合,同时是判定当前分组是否属于已经锁定分组,相应的在页面中对当前分组是显示解锁按钮还是锁定按钮
鉴于涉及到这么多的请求,一个请求完成后需要完成几个请求才能进行相应参数,同时需要请求的先后顺序很明确。
刚开始考虑就用Promise,因为封装的ajax方法就是基于axios的,但涉及到这么多连续请求先别说结构不友好,可能出错你都不知道怎么出的,所以我打算用async和await:
当然咱们还是先把上面的那个例子改一下再说项目中的问题:
function f1() {
console.log('f1')
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, 1000);
})
}
function f2() {
console.log('f2')
return new Promise(resolve2 => {
setTimeout(() => {
resolve2()
}, 1000);
})
}
function f3() {
console.log('f3');
}
async function fn() {
console.log('fn');
await f1()
await f2()
f3()
}
fn()
// fn
// f1
// f2
// f3
这样写代码是不是看起来清爽多了!
今天写逻辑的时候犯了一个影响智商的错看代码和执行效果
在公司写的代码不变贴上来,所以就来模拟一下函数的执行
async function fn() {
await f1()
console.log('fn')
}
function f1() {
new Promise(resolve => {
setTimeout(() => {
console.log('f1')
resolve()
}, 1000);
})
}
fn()
// fn
// f1
async function fn() {
await f1()
console.log('fn')
}
function f1() {
return new Promise(resolve => {
setTimeout(() => {
console.log('f1')
resolve()
}, 1000);
})
}
fn()
// f1
// fn
看看两次执行的结果
第一个函数块写的时候完全不按异步执行去操作,总是像一般函数那样,await当成了异步队列,我只想说明注意的一个点事用Promise时的return。
我们应该了解下面这些事:
Promise有一个性质就是立即执行,我们需要将它外包一层函数(就叫fn),当我们await fn(),请记住fn只是为了不让promise立即执行,所以我们一定得在fn函数中返回promise,如果不返回,应该知道没有返回值的函数的执行结果是undefined,还执行个毛线呀!
记住函数没有返回值执行结果就是undefined!!!!
同时还有一个比较常见的问题:
async getList() {
fetchList(this.listQuery).then(
({data}) => {
let list = data.list
this.softTotalNum = data.total
// 获取黑名单list
await this.getBlackList()
list.map(item => {
if (this.soft_ids.indexOf(item.soft_id) >= 0) {
item['is_hidden'] = 1
}
return item
})
this.list = list;
})
},
其实应该这样写
getList() {
fetchList(this.listQuery).then(
async ({data}) => {
let list = data.list
this.softTotalNum = data.total
// 获取黑名单list
await this.getBlackList()
list.map(item => {
if (this.soft_ids.indexOf(item.soft_id) >= 0) {
item['is_hidden'] = 1
}
return item
})
this.list = list;
})
},
一定要在你用的await的最近的父级用async声明异步函数,其他的顶级父级没有必要写async
今天遇到坑后总结了下面三点:
- async是一个异步函数声明词,await必须在async函数中使用,await后面应该你用一个延时函数,当然你用一个一般函数也行,就是立即执行而已。根据单词的字面意思,await我们可以理解为必须等我后面的函数执行完之后,下面的代码才能运行。
- await的最近父级必须是async函数,否则会报“await is a reserved word”错。
- await对应的如果类似于promise的函数,鉴于promise的立即执行的特点,我们需要将它外包一层函数(就叫fn),当我们await fn(),请记住fn只是问了不让promise立即执行,所以我们一定得在fn函数中返回promise,如果不返回,应该知道没有返回值的函数的执行结果是undefined,还执行个毛线呀!
以上就是一些按async和await的一些知识,当然这是es7的东西,记得babel编译!!!