前端处理并发的最佳实践

简介: 因为js是单线程的,所以前端的并发指的是在极短时间内发送多个数据请求,比如说循环中发送ajax。

您好,如果喜欢我的文章,可以关注我的公众号「量子前端」,将不定期关注推送前端好文~

什么是并发?

因为js是单线程的,所以前端的并发指的是在极短时间内发送多个数据请求,比如说循环中发送ajax。

举一个简单的例子:

下面一段代码是常规的mount阶段执行的请求:

useEffect(async () => {
   
    console.time();
    await TaskBizService.querySpyTaskSummary();
    await TaskBizService.querySpyTask();
    console.timeEnd();

    // time: 300ms
}, [])

更换成这样:

useEffect(() => {
   
    console.time();
    Promise.all([
        TaskBizService.querySpyTaskSummary(),
        TaskBizService.querySpyTask(),
    ]).then((res) => {
   
        console.timeEnd();
    });

    // time: 120ms
}, [])

可以看出有很大的性能优化空间和区别,如果在页面渲染时,多个请求没有相互数据依赖性(依赖请求),直接采用并行请求会加快页面中数据展现的时间,这两个demo同时也涉及到事件循环的一些知识。

因此也可以在页面中找到一些可优化的请求,转换为并行,对于首屏渲染的优化是有很大帮助的。

Promise.all

可以采用Promise.all处理并发, 当所有promise全部成功时, 会走.then,并且可以拿到所有promise中传进resolve中的值。

看一下这段代码:

let p1 = new Promise((resolve, reject) => {
   
  setTimeout(() => {
   
      resolve('request1 end')
  }, 1000);
})

let p2 = new Promise((resolve, reject) => {
   
  setTimeout(() => {
   
      resolve('request2 end')
  }, 3000);
})

console.time(); // 开始计时
Promise.all([p1, p2])
.then(result => {
   
  console.timeEnd(); // default: 3.2s
  console.log(result); // (2) ['request1 end', 'request2 end']  
})

如果Promise.all中有实例失败,整个并发会全部挂掉。

就像这段代码:

let p1 = new Promise((resolve, reject) => {
   
  setTimeout(() => {
   
      resolve('request1 end')
  }, 1000);
})

let p2 = new Promise((resolve, reject) => {
   
  setTimeout(() => {
   
      reject('request2 fail')
  }, 3000);
})

console.time(); // 开始计时
Promise.all([p1, p2])
.then(result => {
   
  // 不会走到这一步
  console.timeEnd(); // default: 3.2s
  console.log(result); 
})

总结

  1. Promise.all在处理并发时,如果有一个promise失败,就不会走.then, 但是如果只是需要在所有异步执行完成之后去执行其它副作用代码,可以把代码写在.finally中实现。

  2. 但是如果需要在有异步失败之后,获取到promise实例通过resolve传过来的值,则不行。

Promise.allSettled

Promise.all中实现不了的功能, 可以使用Promise.allSettled来实现, 在Promise.allSettled中,当其中有一个promise执行失败时,会继续走.then, 并且可以同时拿到传给resolve、reject的值,可以通过回调值来判断接口的请求结果来做二次处理。

代码改造起来也非常简单,如下:

let p1 = new Promise((resolve, reject) => {
   
  setTimeout(() => {
   
      resolve('request1 end')
  }, 1000);
})

let p2 = new Promise((resolve, reject) => {
   
  setTimeout(() => {
   
      resolve('request2 end')
  }, 3000);
})

console.time(); // 开始计时
Promise.allSettled([p1, p2])
.then(result => {
   
  console.timeEnd(); // default: 3.2s
  console.log(result); // (2) ['request1 end', 'request2 end']  
})

当请求结果全部resolve的情况,Promise.allSettledPromise.all没有太大区别,只是对于catch的情况下做了一次弥补。

同时Promise.allSettled的兼容性并没有Promise.all来得好。

总结

  1. Promise.allSettled在处理并发时,不怕异步执行失败,继续会走.then,完美解决Promise.all在并发有失败情况下,拿不到值的情况

  2. 唯一的缺点,比起Promise.all兼容性差一点,多了两个不支持的浏览器, 安卓端火狐浏览器(Firefox for Android)及 Samsung Internet 浏览器。

async/await

async/await能处理并发吗?答案是可以的,但是代码会看起来臃肿不易读一下,先看一下常规的async/await的异步处理方案吧。

代码如下:

let getData1 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('request1 end')
    }, 2000);
  })
};

let getData2 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('request2 end')
    }, 3000);
  })
}
let syncFn = async () => {
    console.time(); // 开始计时

    const data1 = await getData1();
    const data2 = await getData2();

    console.timeEnd(); // default: 5.8s
    console.log(data1, data2); // request1 end, request2 end
}

syncFn();

从耗时可以看到,async/await造成了异步阻塞,实际走的是串行(依赖)请求,也是普通项目中最常见的处理方式,接下来我们改造一下async/await并行请求的方案。

let getData1 = () => {
   
  return new Promise((resolve, reject) => {
   
    setTimeout(() => {
   
      resolve('request1 end')
    }, 2000);
  })
};

let getData2 = () => {
   
  return new Promise((resolve, reject) => {
   
    setTimeout(() => {
   
      resolve('request2 end')
    }, 3000);
  })
}
let syncFn = async () => {
   
    console.time(); // 开始计时

    const p1 = getData1();
    const p2 = getData2();

    const data1 = await p1;
    const data2 = await p2;

    console.timeEnd(); // default: 3.8s
    console.log(data1, data2); // request1 end, request2 end
}

syncFn();

总结

  1. 如果是在安装了axios或者其它请求库,在这些库本身提供支持并发的api情况下,如axios.all、axios.spread,建议使用他们提供的这些api,因为作为一个百万级下载量的库,提供的这些api考虑到的边界条件、兼容性肯定是比其它原生方法好用的。

  2. 在没有这些库的情况下,推荐使用Promise.allSettled,如果你喜欢用 async、await 也可以,结果没有区别,看个人喜好了。但是在我看来在处理并发的情况下 async、await的写法感觉就不那么简洁了。

目录
相关文章
|
7月前
|
编解码 前端开发 JavaScript
Web 前端开发中的最佳实践
本文将介绍 Web 前端开发中的最佳实践,包括代码组织、性能优化、响应式设计和用户体验等方面。通过遵循这些实践,开发人员可以提高开发效率,优化用户体验,并减少潜在的问题和错误。
|
7月前
|
前端开发 JavaScript UED
如何优化前端网页加载速度:最佳实践与技巧
本文探讨了如何通过优化前端网页加载速度来提升用户体验和网站性能。从资源压缩和合并、减少HTTP请求、优化图片、使用CDN加速、采用异步加载和延迟加载等方面介绍了一系列最佳实践和技巧,帮助开发者更好地优化前端性能,提升网页加载速度。
|
24天前
|
存储 前端开发 JavaScript
前端中对象的深度应用与最佳实践
前端对象应用涉及在网页开发中使用JavaScript等技术创建和操作对象,以实现动态交互效果。通过定义属性和方法,对象可以封装数据和功能,提升代码的组织性和复用性,是现代Web开发的核心技术之一。
|
1月前
|
前端开发 数据管理 测试技术
前端自动化测试:Jest与Cypress的实战应用与最佳实践
【10月更文挑战第27天】本文介绍了前端自动化测试中Jest和Cypress的实战应用与最佳实践。Jest适合React应用的单元测试和快照测试,Cypress则擅长端到端测试,模拟用户交互。通过结合使用这两种工具,可以有效提升代码质量和开发效率。最佳实践包括单元测试与集成测试结合、快照测试、并行执行、代码覆盖率分析、测试环境管理和测试数据管理。
57 2
|
1月前
|
前端开发 JavaScript 数据可视化
前端自动化测试:Jest与Cypress的实战应用与最佳实践
【10月更文挑战第26天】前端自动化测试在现代软件开发中至关重要,Jest和Cypress分别是单元测试和端到端测试的流行工具。本文通过解答一系列问题,介绍Jest与Cypress的实战应用与最佳实践,帮助开发者提高测试效率和代码质量。
43 2
|
2月前
|
缓存 前端开发 JavaScript
探索现代前端开发:从框架选择到最佳实践
【10月更文挑战第11天】探索现代前端开发:从框架选择到最佳实践
41 0
|
4月前
|
前端开发 JavaScript 大数据
React与Web Workers:开启前端多线程时代的钥匙——深入探索计算密集型任务的优化策略与最佳实践
【8月更文挑战第31天】随着Web应用复杂性的提升,单线程JavaScript已难以胜任高计算量任务。Web Workers通过多线程编程解决了这一问题,使耗时任务独立运行而不阻塞主线程。结合React的组件化与虚拟DOM优势,可将大数据处理等任务交由Web Workers完成,确保UI流畅。最佳实践包括定义清晰接口、加强错误处理及合理评估任务特性。这一结合不仅提升了用户体验,更为前端开发带来多线程时代的全新可能。
103 1
|
4月前
|
缓存 前端开发 JavaScript
优化前端性能的十大最佳实践
在现代网页开发中,前端性能优化不仅仅是为了提升用户体验,还能显著提高网站的加载速度和响应时间。本文探讨了十大最佳实践,从优化资源加载到减少网络请求,再到提高页面渲染效率,每个实践都旨在解决常见的性能瓶颈。通过实现这些策略,开发者可以显著提升前端性能,提升用户满意度,并确保网站在各种设备上的流畅运行。
|
4月前
|
缓存 前端开发 JavaScript
高效开发现代 Web 应用:从前端到后端的最佳实践
在开发现代 Web 应用时,前端和后端技术的选择对项目的性能、可维护性和用户体验至关重要。本文将探讨如何通过现代工具和框架来优化前端和后端开发流程。我们将分析前端技术(如 React 和 Vue.js)与后端技术(如 Node.js 和 Django)的集成,并提供实际案例来展示如何实现高效开发。无论是对新手还是经验丰富的开发者,本指南都提供了宝贵的洞见和实用的技巧,以帮助提高开发效率并构建出色的 Web 应用。
|
4月前
|
缓存 前端开发 API
优化前端性能的最佳实践
在构建高效的前端应用时,性能优化是不可忽视的关键因素。本文将探讨一些实用的前端性能优化策略,包括资源的懒加载、合理的缓存策略、代码分割以及异步数据加载等方法。通过实施这些最佳实践,可以显著提升应用的响应速度和用户体验。
下一篇
DataWorks