1. 首页

如何编写高质量可维护的代码——异步优化

前言

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

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

如何编写高质量可维护的代码——异步优化

异步实现种类

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

callback

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

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

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

 /* Js中文网 - 全球前端挚爱的技术成长平台 https://www.javascriptc.com/ */

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

Promise

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

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

 /* Js中文网 - 全球前端挚爱的技术成长平台 https://www.javascriptc.com/ */

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 调用异步的时候是从上到下,顺序执行,就像在写同步代码一样,这更加的符合我们编写代码的习惯和思维逻辑,所以容易理解。 整体代码逻辑也会更加的清晰。

 /* Js中文网 - 全球前端挚爱的技术成长平台 https://www.javascriptc.com/ */

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 中的一个新东西,我相信很多人在现实的代码中很少能接触到它,所以它相对而言对大家来说还是比较晦涩,但是这家伙还是很强的,简单来说它能控制异步调用,并且其实是一个状态机。

 /* Js中文网 - 全球前端挚爱的技术成长平台 https://www.javascriptc.com/ */
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 的方式去实现自动检查上述两异步检查。

 /* Js中文网 - 全球前端挚爱的技术成长平台 https://www.javascriptc.com/ */

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 的返回之后再继续调用,所以在这样有先后关系的时候就存在了一个叫回调地狱的问题了。

 /* Js中文网 - 全球前端挚爱的技术成长平台 https://www.javascriptc.com/ */

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

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

 /* Js中文网 - 全球前端挚爱的技术成长平台 https://www.javascriptc.com/ */

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

异步循环

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

并行执行

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

 /* Js中文网 - 全球前端挚爱的技术成长平台 https://www.javascriptc.com/ */

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

顺序执行

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

  1. 使用 async/await 配合 for
 /* Js中文网 - 全球前端挚爱的技术成长平台 https://www.javascriptc.com/ */

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

  1. 使用 async/await 配合 while
 /* Js中文网 - 全球前端挚爱的技术成长平台 https://www.javascriptc.com/ */

//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
 /* Js中文网 - 全球前端挚爱的技术成长平台 https://www.javascriptc.com/ */

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

  1. 使用递归
 /* Js中文网 - 全球前端挚爱的技术成长平台 https://www.javascriptc.com/ */

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)

结尾

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

参考文献

JS 异步编程六种方案

Async/Await替代Promise的6个理由

Javascript异步编程的4种方法

作者:政采云前端团队
链接:https://segmentfault.com/a/1190000038192701

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:如何编写高质量可维护的代码——异步优化

链接:https://www.javascriptc.com/4799.html

« 别光知道用console.log调试了,快来试试这些高效的调试方法!
为开源项目做贡献的10个步骤»
Flutter 中文教程资源

相关推荐

QR code