AI实践哪家强?来 AICon, 解锁技术前沿,探寻产业新机! 了解详情
写点什么

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

  • 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:002403

评论

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

第十二周课后作业

Binary

ElasticSearch.01-简介

insight

elasticsearch 2月春节不断更

熬夜7天,我总结了JavaScript与ES的25个重要知识点!

我是哪吒

学习 程序员 面试 大前端 2月春节不断更

深入浅出函数式编程:Stream流水线的实现原理

李尚智

Java 架构 微服务

【STM32】串口通信---用代码与芯片对话

AXYZdong

硬件 stm32 2月春节不断更

架构师训练营 4 期 第7周

引花眠

架构师训练营 4 期

Scrum Patterns:梳理产品待办列表(译)

Bruce Talk

敏捷开发 译文 Agile Scrum Patterns

面试官系列:你对Spring事件发布和广播监听有了解吗?

后台技术汇

面试 2月春节不断更

第十二周学习总结

Binary

Tomcat异常: Unable to process Jar entry [module-info.class] from Jar

小马哥

Java maven 七日更 二月春节不断更

盘点关于程序员的那些经典案例

孙叫兽

程序员 程序人生 话题讨论 薪水 计算机原理

Chrome浏览器多进程架构3个必会知识点

梁龙先森

面试 大前端 浏览器

【LeetCode】情侣牵手Java题解

Albert

算法 LeetCode 2月春节不断更

日记 2021年2月14日(周日)

Changing Lin

2月春节不断更

还傻傻分不清楚equals和==的区别吗?看完就明白了

codevald

Java 源码分析 string Object

《我们脑中挥之不去的问题》 - 卓克科普(2)

石云升

读书笔记 科普 2月春节不断更

「架构师训练营 4 期」 第七周 - 001&2

凯迪

架构师训练营 4 期

JDBC速查手册

jiangling500

Java JDBC

Spring框架源码:BeanFactory与Bean的生命周期

程序员架构进阶

Java spring 源码阅读 七日更 2月春节不断更

10. 比找女朋友还难的技术点,Python 面向对象

梦想橡皮擦

Python 2月春节不断更 python入门

【STM32】串口通信出现乱码(使用官方标准库)

AXYZdong

硬件 stm32 2月春节不断更

Elasticsearch dynamic mapping

escray

elastic 七日更 死磕Elasticsearch 60天通过Elastic认证考试 2月春节不断更

熬夜总结了 “HTML5画布” 的知识点(共10条)

我是哪吒

学习 读书笔记 程序员 随笔杂谈 2月春节不断更

9. Python 学习过程的第一个山坡,99%的人都倒在了山坡下

梦想橡皮擦

Python 2月春节不断更 python入门 python学习

第十二周 数据应用一 作业 「架构师训练营 3 期」

胡云飞

Java SE最佳实践

jiangling500

Java 最佳实践 Java SE

Tomcat速查手册

jiangling500

Java tomcat

聊聊大公司创新的机制:饱和攻击

boshi

创新 七日更

杨明越:Kubernetes的下一仗可能是提升标准化程度

杨明越

SpringMVC专栏 第1篇 - 快速入门

小马哥

Java spring Spring MVC 七日更 二月春节不断更

深入理解gradle中的task

程序那些事

Java maven Gradle 程序那些事 构建工具

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