写点什么

编写高质量可维护的代码:异步优化

  • 2021-07-31
  • 本文字数:3634 字

    阅读完需:约 12 分钟

编写高质量可维护的代码:异步优化

前言


在现在前端开发中,异步操作的频次已经越来越高了,特别对于数据接口请求和定时器的使用,使得我们不得不关注异步在业务中碰到的场景,以及对异步的优化。错误的异步处理可能会带来很多问题,诸如页面渲染、重复加载等问题。


下面我们就先简单的从 JavaScript 中有大致的哪几种异步类型为切入点,然后再列举一些业务中我们会碰到的场景来逐个分析下,我们该如何解决。


异步实现种类


首先关于异步实现的方式上大致有如下几种:


callback


callback 即回调函数。这家伙出现很早很早了,他其实是处理异步的基本方法。并且回调的概念不单单出现在 JavaScript,你也会在 Java 或者 C# 等后端语言中也能找到他的影子。


回调函数简单的说其实就是给另外一个寄主函数作为传参的函数。在寄主函数执行完成或者执行到特定阶段之后触发调用回调函数并执行,然后把执行结果再返回给寄主函数的过程。


比如我们熟悉的 setTimeout 或者 React 中的 setState 的第二个方法都是以回调函数方式去解决异步的实现。


setTimeout(() => {   //等待0.2s之后再做具体的业务操作   this.doSomething();}, 200);this.setState({  count: res.count,}, () => {  //在更新完count之后再做具体的业务操作  this.doSomething();});
复制代码

Promise


Promise 是个好东西,有了它之后我们可以对异步进行很多操作,并且可以把异步以链式的方式进行操作。


其实在 JQuery 中的 deferred 和它就有点像,都是采用回调函数的解决方案,都可以做链式调用,但是在 Promise 中增加了错误的 catch 方法可以更加方便的处理异常场景,并且它内置状态(resolve, reject,pending),状态只能由 pending 变为另外两种的其中一种,且改变后不可逆也不可再度修改。


let promise = new Promise((resolve, reject) => {   reject("对不起,你不是我的菜");});promise.then((data) => {console.log('第一次success' + data);  return '第一次success' + data},(error) => {console.log(error) }).then((data2) => {  console.log('第二次success' + data2);},(error2) => {   console.log(error2) }).catch((e) => {  console.log('抓到错误啦' + e);});
复制代码

await/async


await/async 其实是 Promise 的一种升级版本,使用 await/async 调用异步的时候是从上到下,顺序执行,就像在写同步代码一样,这更加的符合我们编写代码的习惯和思维逻辑,所以容易理解。整体代码逻辑也会更加的清晰。


async function asyncDemoFn() {  const data1 = await getData1();  const data2 = await getData2(data1);  const data3 =  await getData3(data2);  console.log(data3)}await asyncDemoFn()
复制代码

generator


generator 中文名叫构造器,是 ES6 中的一个新东西,我相信很多人在现实的代码中很少能接触到它,所以它相对而言对大家来说还是比较晦涩,但是这家伙还是很强的,简单来说它能控制异步调用,并且其实是一个状态机。


function* foo() {  for (let i = 1; i <= 3; i++) {    let x = yield `等我一下呗,i = ${i}`;    console.log(x);  }}setTimeout(() => {  console.log('终于轮到我了');}, 1);var a = foo();console.log(a); // foo {<closed>}var b = a.next();console.log(b); // {value: "等我一下呗,i = 1", done: false}var c = a.next();console.log(c); // {value: "等我一下呗,i = 2", done: false}var d = a.next();console.log(d); // {value: "等我一下呗,i = 3", done: false}var e = a.next();console.log(e); // {value: undefined, done: true}// 终于轮到我了
复制代码


上面代码的函数 foo 是一个协程,它的厉害的地方就是 yield 命令。它表示执行到此处,执行权将交给其他协程。也就是说,yield 命令是异步两个阶段的分界线。


协程遇到 yield 命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。它的最大优点,就是代码的写法非常像同步操作,如果去除 yield 命令,简直一模一样。


再来个有点贴近点场景方式来使用下 generator。比如现在在页面中我们需要自动的执行 checkAuth 和 checkAddress 检查,我们就用 generator 的方式去实现自动检查上述两异步检查。


const checkAuth = () => {    return new Promise((resolve)=>{        setTimeout(()=>{           resolve('checkAuth1')         },1000)    })}const checkAddress = () => {    return new Promise((resolve)=>{        setTimeout(()=>{            resolve('checkAddress2')        },2000)    })}var steps = [checkAuth,checkAddress]function* foo(checkList) {  for (let i = 0; i < checkList.length; i++) {    let x = yield checkList[i]();    console.log(x);  }}var stepsGen = foo(steps)var run = async (gen)=>{    var isFinnish = false    do{       const {done,value} = gen.next()       console.log('done:',done)       console.log('value:',value)       const result = await value       console.log('result:',result)              isFinnish = done    }while(!isFinnish)    console.log('isFinnish:',isFinnish)}run(stepsGen)
复制代码


种类对比


  • 从时间维度从早到晚:callback,Promise,generator,await/async

  • await/async 是目前对于异步的终极形式

  • callback 让我们有了基本的方式去处理异步情况,Promise 告别了 callback 的回调地狱并且增加 resolve,reject 和 catch 等方法让我们能处理不同的情况,generator 增加了对于异步的可操作性,类似一个状态机可暂时停住多个异步的执行,然后在合适的时候继续执行剩余的异步调用,await/async 让异步调用更加语义化,并且自动执行异步


异步业务中碰到的场景

回调地狱


在使用回调函数的时候我们可能会有这样的场景,B 需要在 A 的返回之后再继续调用,所以在这样有先后关系的时候就存在了一个叫回调地狱的问题了。


getData1().then((resData1) => {  getData2(resData1).then((resData2) => {    getData3(resData2).then((resData3)=>{      console.log('resData3:', resData3)    })  });});
复制代码


碰到这样的情况我们可以试着用 await/async 方式去解这种有多个深层嵌套的问题。


async function asyncDemoFn2() {  const resData1 = await getData1();  const resData2 = await getData2(resData1);  const resData3 =  await getData3(resData2);  console.log(resData3)}await asyncDemoFn2()
复制代码


异步循环


在业务中我们最最经常碰到的就是其实还是存在多个异步调用的顺序问题,大致上可以分为如下几种:


并行执行


在并行执行的时候,我们可以直接使用 Promise 的 all 方法


Promise.all([getData1(),getData2(),getData3()]).then(res={console.log('res:',res)})
复制代码


顺序执行


在顺序执行中,我们可以有如下的两种方式去做


  1. 使用 async/await 配合 for

const sources = [getData1,getData2,getData3]async function promiseQueue() {  console.log('开始');  for (let targetSource in sources) {    await targetSource();  }  console.log('完成');};promiseQueue()
复制代码


  1. 使用 async/await 配合 while

// getData1,getData2,getData3 都为 promise 对象const sources = [getData1,getData2,getData3]async function promiseQueue() {  let index = 0  console.log('开始');  while(index >=0 && index < sources.length){    await targetSource();    index++  }  console.log('完成');};promiseQueue()
复制代码


  1. 使用 async/await 配合 reduce

// getData1,getData2,getData3 都为 promise 对象const sources = [getData1,getData2,getData3]sources.reduce(async (previousValue, currentValue)=>{  await previousValue  return currentValue()},Promise.resolve())
复制代码


  1. 使用递归


const sources = [getData1,getData2,getData3]function promiseQueue(list , index = 0) {  const len = list.length  console.log('开始');  if(index >= 0 && index < len){    list[index]().then(()=>{      promiseQueue(list, index+1)          })  }  console.log('完成');}promiseQueue(sources)
复制代码


结尾


今天只是关于异步的普通使用场景的讨论,并且做了些简单的例子。其实关于异步的使用还有很多很多复杂的使用场景。更多的奇思妙想正等着你。


头图:Unsplash

作者:毅轩

原文:https://mp.weixin.qq.com/s/s6fVoY31MqUXrW8RPka3pA

原文:编写高质量可维护的代码:异步优化

来源:政采云前端团队 - 微信公众号 [ID:Zoo-Team]

转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2021-07-31 10:002338

评论

发布
暂无评论
发现更多内容

企业内部统一的移动平台,实现安全高效的业务移动化

BeeWorks

天翼云混合云容灾技术解析

天翼云开发者社区

嘉为科技吴文豪:重塑运维系统,跨越烟囱式建设的陷阱

嘉为蓝鲸

运维 #WeOps

天翼云Serverless边缘容器下沉服务 促进企业聚焦业务创新

天翼云开发者社区

制造业的敏捷分析,还需要使用瓴羊Quick BI

对不起该用户已成仙‖

面试官:说说电商系统订单超时自动取消怎么实现?你有几种方案?

程序员小毕

程序员 面试 程序人生 后端 架构师

APP以监听投广?中央APP治理专项组测评揭晓答案!

科技热闻

深究用户模式锁的使用场景(异变结构、互锁、旋转锁)

C++后台开发

数据结构 多线程 linux开发 C++开发

图数据技术护航网络安全

Neo4j 图无处不在

网络安全 neo4j 图数据库 知识图谱 图算法

数字产业化的颠覆创新和生态打法

PMO实践

产业数字化 11月月更

企业数字营销和运营如何效果更好?瓴羊Quick BI成为了不错的选择

小偏执o

老板让我在Linux中使用traceroute排查服务器网络问题,幸好我收藏了这篇文章!

wljslmz

Linux 网络故障 11月月更 traceroute

瓴羊Quick BI在商业智能BI发展趋势方面如何?

对不起该用户已成仙‖

可视化:如何选择数据可视化图表?

2D3D前端可视化开发

数据可视化 可视化大屏 可视化图表 可视化工具 sovitchart

微服务治理的3种方式

穿过生命散发芬芳

微服务治理 11月月更

生活中常见的新北洋打印机:多场景赋能美好生活

科技热闻

Kotlin作用域函数[Scope Function](下)

子不语Any

android kotlin 11月月更

云原生主题学习月|成为社区领学员,解锁专属奖励及超多数量社区学员奖品!

亚马逊云科技 (Amazon Web Services)

云原生 培训与认证

焱融科技为国家重点实验室打造海量高性能存储

焱融科技

云计算 分布式系统 高性能 文件存储

通过飞书审批 Bytebase 工单

Bytebase

数据库 sql DevOps SQL审核 SQL审批

数字先锋| 一朵云,将温暖传递千家万户

天翼云开发者社区

亚信科技亮相南京软博会,数智赋能百行千业

亚信AntDB数据库

AntDB aisware antdb AntDB数据库

老工程师荐读!PCB设计避坑指南

华秋PCB

PCB PCB设计

Kotlin作用域函数[Scope Function](上)

子不语Any

android kotlin 11月月更

元器选型攻略之 电阻

元器件秋姐

元器件知识 元器件科普 电阻 电阻定义 常见电阻

Kotlin伴生对象(Companion Object)

子不语Any

android kotlin 11月月更

云原生主题学习月|共同学习全球领先的亚马逊云科技云原生课程,组团共学拿奖励~

亚马逊云科技 (Amazon Web Services)

云原生 培训与认证

Neo4j CEO Emil Eifrem 解读图数据平台引领数据库未来十年的发展

Neo4j 图无处不在

neo4j 图数据库 知识图谱 图可视化引擎 图数据

嘉为科技张敏:一文讲清场景工程方法论及运维组织能力内化

嘉为蓝鲸

运维 数字化

ZooKeeper 避坑实践:如何调优 jute.maxbuffer

阿里巴巴云原生

zookeeper 阿里云 云原生

玩转云端| 数据库安全服务,为数据库资产构建“最后一道防线”!

天翼云开发者社区

编写高质量可维护的代码:异步优化_语言 & 开发_政采云前端团队_InfoQ精选文章