你知道JS异步遍历的方法吗?

简介: 前言JavaScript 异步编程可以说是一大核心知识。我们都知道 JavaScript 是一个单线程语言,单线程机制有什么好处和坏处非常的明显,这里就不多说。异步编程思想的出现,让 JS 这个单线程语言又增添了一丝色彩。异步 JS 带来了非常多的好处,但是如果你没有真正的掌握,那可能就会遇到一些奇奇怪怪的问题,比如我们今天要说的异步遍历的问题。

问题背景:

我们通常使用 for 循环或者 forEach 进行遍历操作,在遍历过程中通常都是做一些同步操作。但是有一些情况是需要在遍历的时候做一些异步操作,比如遍历发送请求,遍历执行 SQL 语句等等这些异步操作,这种情况就有一些问题出现!

今天就来学习以下 JS 异步遍历。


1.经典面试题


有一道非常经典的面试题,虽然它重点考察的是闭包的问题,但是它和 JS 的异步有着不可分割的关系。


问题:


请问下列代码的打印结果?

示例代码:

<script>
  for (var i = 0; i < 5; i++) {
    setTimeout(() => {
      console.info(i)
    }, 1000);
  }
</script>

输出结果:113.png

上段代码中,我们正常的理解应该是输出 0、1、2、3、4。但是结果却是输出了 5 个 5。这儿有很大部分原因就是因为 setTimeout 是异步的原因。我们每次 for 循环虽然执行了 setTimeout,但是它里面的函数没有执行,知道 for 循环完毕,才会执行里面的函数,此时的 i 已经变为了 5。


2.进入正题


上一节我们举例了一个比较经典的面试题,目的就是为了让大家先简单理解一下异步。接下来我们模拟实际项目中很容易出现的情况,或者说面试中非常容易被问到的问题。


问题背景:

有一个存放了很多异步操作的数组,你如何将它们依次执行和返回结果?


示例代码:

<script>
  // 模拟异步操作,实际可能为发送请求等等
  function createPromise(time, value) {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(value);
      }, time);
    })
  }
  let asyncArray = [createPromise(3000, "函数 1"), createPromise(1000, "函数 2"), createPromise(5000, "函数 3")]; // 定义异步函数数组
  // 里面执行异步操作
  async function asyncTest() {
    console.time();
    console.info("start");
    // 执行异步函数数组
    // 请编写你的代码
    console.info("end");
    console.timeEnd();
  }
  asyncTest();
</script>


上段代码中 createPromise 函数简单模拟了一个异步操作,然后我们声明了一个 asyncArray 数组,里面存放的是很多异步操作函数,每个函数返回的都是 promise 对象。asyncTest 函数用来执行我们数组里面的异步函数。


看到上面那道题目,应该还是有很多人比较熟悉,但是有些小伙伴如果对异步还不了解,或者对 promise 或者 async/await 不熟悉的话,那么可能有点无从下手,不知道面试官想问什么?


解题思路:

  • 循环数组,分别执行函数
  • 返回 promise,那可以使用 async/await
  • 依次返回结果则需要我们串行执行,可以利用 async/await 解决


通过上面的思路,我们有多种解决方案,一起来实现一下。


3.forEach 循环?


这是很多初学者最容易想到得到一种办法,这道题看似很简单,无非就是循环数组,执行函数嘛!那我们一起来看看结果是什么?

示例代码:

<script>
  // 模拟异步操作,实际可能为发送请求等等
  function createPromise(time, value) {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(value);
      }, time);
    })
  }
  let asyncArray = [createPromise(3000, "函数 1"), createPromise(1000, "函数 2"), createPromise(5000, "函数 3")]; // 定义异步函数数组
  // 里面执行异步操作
  async function asyncTest() {
    console.time();
    console.info("start");
    // 执行异步函数数组
    // forEach
    asyncArray.forEach(async (item) => {
      const res = await item;
      console.info("执行的函数是:",res);
    });
    console.info("end");
    console.timeEnd();
  }
  asyncTest();
</script>

上段代码好像乍一看没有什么问题!利用循环,依次执行异步函数,然后利用 await 阻塞,达到串行目的。我们一起来看看打印结果


输出结果:114.png


结果似乎和我们想的不太一样啊!它似乎是以同步的逻辑执行的,先执行了 console,在执行的异步函数。


造成这种结果的原因很简单,因为 forEach 循环根本没有处理异步操作,它根本不支持异步写法。佛 forEach 的原理很简单,它就是简单的执行了一下我们传入的回调函数,并不会去处理异步情况。


注意:这是很多初学者容易范的错误,一定要注意!出了 forEach 外,类似于 map 等直接传入回调函数的循环方式都无法处理异步。



4.for...of


既然 forEach 循环不行,那我们就换一种能行的循环。for...of 循环可以处理异步操作。

示例代码:


for (const item of asyncArray) {
  const res = await item;
  console.info("执行的函数是:",res);
}


输出结果:115.png


上段代码就满足我们的要求了,依次输出了 1、2、3,即使每个异步函数的处理时间不一样,但是由于使用了 await 阻塞,所以返回的结果也是按照顺序来的。

到这里看起来 for...of 用来遍历异步数组是没有问题的,但是,真实情况是 for...of 还是存在问题,修改一下我们的代码。


修改代码如下:

for (const item of asyncArray) {
  // const res = await item;
  console.info("执行的函数是:",await item.then((res) => {
    console.info(res)
  }));
}

输出结果:116.png


上段代码中,我们在 then 里面做了一些其它操作,且没有返回值,那么我们的输出就会有问题,所以说我们 for..of 的方法还是不太完美。


5.for...await...of


for...await...of 是 ES2018 的新特性,它可以针对异步集合进行操作,它的使用方法基本上和 for..of 一致,只不过多了一个 await 关键词。


示例代码:

for await (const item of asyncArray) {
  console.info("执行的函数是:",item);
}

输出结果:

117.png

上面的输出结果是想要的,而且我们在循环内部没有使用 await 关键词,它不仅可以暂停循环,还允许你做任何操作


6.Promise.all


上面使用 for...of 的方式处理异步的时候是串行方式,也就是说上一个执行完后在执行下一个。假如有这么一个场景,用户上传很多张图片,我们需要图片并行上传。如果串行上传的话,那用户等待的时间将会大大增长,这是不科学的。


使用 Promise.all 就可以让我们的异步函数并行执行。


示例代码:

console.info(await Promise.all(asyncArray))

输出结果:

119.png

如果你运行了上段代码,你会发现控制台是一起返回了三个结果,而使用 for...of 的时候,控制台是依次打印出的结果。


总结


本篇文章我们重点讲的是如何遍历和处理异步数组,主要介绍了for...await..of的方式。大家需要理解串行和并行的概念,当然,本篇文章主要以串行为主,并行的话只介绍了promise.all。


最主要的是大家要掌握for...await...of的用法。


想要视频学习,可以移步B站:小猪课堂




相关文章
|
1月前
|
JavaScript 前端开发 程序员
前端原生Js批量修改页面元素属性的2个方法
原生 Js 的 getElementsByClassName 和 querySelectorAll 都能获取批量的页面元素,但是它们之间有些细微的差别,稍不注意,就很容易弄错!
|
1月前
|
监控 JavaScript Java
Node.js中内存泄漏的检测方法
检测内存泄漏需要综合运用多种方法,并结合实际的应用场景和代码特点进行分析。及时发现和解决内存泄漏问题,可以提高应用的稳定性和性能,避免潜在的风险和故障。同时,不断学习和掌握内存管理的知识,也是有效预防内存泄漏的重要途径。
135 52
|
1月前
|
JSON 前端开发 JavaScript
在 JavaScript 中,如何使用 Promise 处理异步操作?
通过以上方式,可以使用Promise来有效地处理各种异步操作,使异步代码更加清晰、易读和易于维护,避免了回调地狱的问题,提高了代码的质量和可维护性。
|
1月前
|
JavaScript 前端开发 索引
js中DOM的基础方法
【10月更文挑战第31天】这些DOM基础方法是操作网页文档结构和实现交互效果的重要工具,通过它们可以动态地改变页面的内容、样式和行为,为用户提供丰富的交互体验。
|
1月前
|
缓存 JavaScript UED
js中BOM中的方法
【10月更文挑战第31天】
|
1月前
|
缓存 JavaScript 前端开发
JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用
本文深入讲解了 JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用。
49 5
|
1月前
|
JavaScript 前端开发
js中的bind,call,apply方法的区别以及用法
JavaScript中,`bind`、`call`和`apply`均可改变函数的`this`指向并传递参数。其中,`bind`返回一个新函数,不立即执行;`call`和`apply`则立即执行,且`apply`的参数以数组形式传递。三者在改变`this`指向及传参上功能相似,但在执行时机和参数传递方式上有所区别。
29 1
|
1月前
|
JavaScript 前端开发
.js方法参数argument
【10月更文挑战第26天】`arguments` 对象为JavaScript函数提供了一种灵活处理参数的方式,能够满足各种不同的参数传递和处理需求,在实际开发中具有广泛的应用价值。
46 7
|
JavaScript 前端开发 数据安全/隐私保护
|
JavaScript 前端开发 数据安全/隐私保护