小帅の技术博客 小帅の技术博客
首页
  • 基础
  • 框架
  • 进阶
  • 工程化
  • NodeJS
  • 脚本
  • 拓展
  • 技术
  • 服务器
程序猿
关于
友链
  • 分类
  • 标签
  • 归档
GitHub

前端小帅

学而不思则罔,思而不学则殆
首页
  • 基础
  • 框架
  • 进阶
  • 工程化
  • NodeJS
  • 脚本
  • 拓展
  • 技术
  • 服务器
程序猿
关于
友链
  • 分类
  • 标签
  • 归档
GitHub
  • 基础

    • 发布订阅模式vs观察者模式
    • 事件通信研究
    • JavaScript数组方法reduce的妙用
      • 前言
      • 基本用法
      • 将数组转换为对象
      • 数组合并 / tree结构或多维数组转换为一维数组
      • 用 reduce 实现 Promise.all 串行执行
        • Promise.all 的基本用法
        • Promise.all + reduce 的用法
        • async await 用法处理promise队列
      • 参考
    • 记忆函数memoize
    • JS类型、类型判断、类型转换
    • 浏览器中JavaScript的运行机制
    • WebSocket连接过程及原理分析
    • 执行上下文
    • 作用域与闭包
    • JavaScript进阶之map与reduce的前世今生
    • JavaScript之new的执行过程
    • 从输入 URL 到页面加载完成发生了什么?
    • 说说call、apply、bind是如何改变this的
  • 工程化

  • 框架

  • 精进

  • 其他

  • 前端
  • 基础
sunss
2021-09-30

JavaScript数组方法reduce的妙用

# 前言

reduce作为JavaScript的数组方法,相比map、forEach、filter等其他常用迭代方法,使用场景和使用频率可能并没有那没高,其中也有朋友钟爱一律for循环、map方法梭哈到底等,不过,作为JavaScript的内置数组方法,肯定有符合它的使用场景的时候,接下来就让我们深入这个小伙伴的内心世界~

# 基本用法

  • 官方描述:

reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。

我们比较熟悉的就是累加器

  • 语法:

array.reduce(callback(accumulator, currentValue, currentIndex, arr), initialValue)

reduce 接收两个参数,分别是:callback回调函数和初始值(可选)

reduce 为数组中的每一个元素依次执行callback函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:

// Accumulator (acc) (累计器)
// currentValue (cur) (当前值)
// Current Index (idx) (当前索引)
// Source Array (src) (源数组)
1
2
3
4

初始值:

作为第一次调用 callback 函数时的第一个参数的值 (accumulator), 如果没有提供初始值,则将使用数组中的第一个元素。 在没有初始值的空数组上调用 reduce 将报错。

注意:

回调函数第一次执行时,accumulator和currentValue的取值有两种情况:如果调用reduce()时提供了initialValue,accumulator取值为initialValue,currentValue取数组中的第一个值;如果没有提供 initialValue,那么accumulator取数组中的第一个值,currentValue取数组中的第二个值。

为了更安全的使用一般建议提供一个初始默认值,如:数组([])、数值累加 (0)、对象({})。

一个简单的数值累加器用法:

const numArr=[0, 1, 2, 3, 4]

function callback(accumulator, currentValue, currentIndex, array){
  return accumulator + currentValue;
}

numArr.reduce(callback,0);

// 10
1
2
3
4
5
6
7
8
9

实际上reduce还有很多重要的用法,这是因为累加器的值可以不必为简单类型(如数字或字符串),它也可以是结构化类型(如数组或对象),这使得我们可以用它做一些其他有用的事情,比如:

  • 将数组转换为对象
  • 展开更大的数组
  • 在一次遍历中进行两次计算
  • 将映射和过滤函数组合
  • 按顺序运行异步函数

# 将数组转换为对象

注意这里:需指定初始值👉 {}

const arr = [
  {
    label: "name",
    value: "AJ",
  },
  {
    label: "age",
    value: 18,
  },
  {
    label: "address",
    value: "上海",
  },
]

const obj = arr.reduce(
  (accumulator, currentValue) => ({ ...accumulator, [currentValue.label]: currentValue.value }),
  {}
)
console.log(obj)
// { name: 'AJ', age: 18, address: '上海' }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 数组合并 / tree结构或多维数组转换为一维数组

# 用 reduce 实现 Promise.all 串行执行

在实际业务中,有时需要异步处理多个任务,这时我们一般会通过数组的形式用Promise.all进行处理.(如:多接口请求、nodejs文件模块多文件读取、图片批量压缩等)

# Promise.all 的基本用法

const reuqest01 = new Promise((resolve) =>
  setTimeout(() => {
    console.log("reuqest01: 3000ms")
    resolve("reuqest01: 3000ms")
  }, 3000)
)

const reuqest02 = new Promise((resolve) =>
  setTimeout(() => {
    console.log("reuqest02: 1000ms")
    resolve("reuqest02: 1000ms")
  }, 1000)
)

const reuqest03 = new Promise((resolve) =>
  setTimeout(() => {
    console.log("reuqest03: 2000ms")
    resolve("reuqest03: 2000ms")
  }, 2000)
)

// 队列
const requstList = [reuqest01, reuqest02, reuqest03]

console.time("time-used:")

Promise.all(requstList)
  .then((res) => {
    console.log("promise.all.then:", res)
    console.timeEnd("time-used:")
  })
  .catch((err) => {
    console.log("promise.all.catch:", err)
  })

// 输出日志

// reuqest02: 1000ms
// reuqest03: 2000ms
// reuqest01: 3000ms
// promise.all.then: [ 'reuqest01: 3000ms', 'reuqest02: 1000ms', 'reuqest03: 2000ms' ]
// time-used:: 3005.211ms
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

耗时为并行时间 3000ms,运行效果图如下:

promise-1

小结:

  1. Promise.all 是并行执行其接收到的 promise 队列
  2. 函数执行结果的响应先后不影响队列的排列
  3. 即使只有一个失败,也会返回整个失败,检测到失败立即判定为失败

Promise.allSettled()方法:成功或失败对其他函数不影响,可以完整的显示每个函数的运行结果

# Promise.all + reduce 的用法

区别:promise函数是在reduce中执行时调用的

// 队列
const requstList = [reuqest01, reuqest02, reuqest03 /*, reuqest04()*/]

requstList.reduce((prev, next) => prev.then(() => next()), Promise.resolve())

// 输出日志
// 同步执行:reuqest01 => reuqest02 => reuqest03

// reuqest01: 3000ms
// reuqest02: 1000ms
// reuqest03: 2000ms
// time-used:: 6006.042ms
1
2
3
4
5
6
7
8
9
10
11
12

reduce 是同步执行的,在一个事件循环就会完成,但这仅仅是在内存快速构造了 Promise 执行队列,展开如下:

new Promise((resolve, reject) => {
  // Promise 1
  resolve()
})
  .then((res) => {
    // Promise 2
    return res
  })
  .then((res) => {
    // Promise 3
    return res
  })
1
2
3
4
5
6
7
8
9
10
11
12

耗时为总计时间 6000ms,运行效果图如下:

promise-2

小结:

  1. promise函数是在执行时调用,和 Prommise.all 有所区别
  2. reduce 初始默认值指定 Promise.resolve() 状态,确保开始执行 .then 的状态
  3. reduce 的作用就是在内存中生成这个队列,而不需要把这个冗余的队列写在代码里!

# async await 用法处理promise队列

  • async await 基本写法:
const fetchPromise = async () => {
  try {
    const res01 = await reuqest01()
    const res02 = await reuqest02()
    const res03 = await reuqest03()

    console.log("sucess:", res01, res02, res03)

    //  res04发生异常后,不会再继续往下执行,控制台是没有 "fail" 打印的
    const res04 = await reuqest04()
    console.log("fail")
  } catch (error) {
    console.log(error)
  }
}

fetchPromise()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

可以发现这种方式比较繁琐,且需要try catch配合处理 err异常问题,当出现异常后便不会继续往下执行了,而是直接进入 catch,而且对于promise队列的处理也不是很方便

  • async await函数 + for of 循环配合写法
async function runPromiseList(promiseList) {
  for (const request of promiseList) {
    try {
      const res = await request()
      console.log("res", res)
    } catch (error) {
      console.log("error:", error)
    }
  }
}

const delay = (time, arg, status = true) =>
  new Promise((resolve, reject) =>
    setTimeout(() => status ? resolve(arg) : reject(arg), time))

// 测试 promise.all 的运行顺序
const reuqest01 = async () => await delay(3000, "reuqest01: 3000ms")
const reuqest02 = async () => await delay(1000, "reuqest02: 1000ms")
const reuqest03 = async () => await delay(2000, "reuqest03: 2000ms")

const reuqest04 = async () => await delay(4000, "reuqest04: 4000ms", false)

runPromiseList([reuqest01, reuqest02, reuqest04, reuqest03])

// 这样也可以实现同步执行
// 进行错误捕获后不影响执行

// res reuqest01: 3000ms
// res reuqest02: 1000ms
// error: reuqest04: 4000ms
// res reuqest03: 2000ms
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

不过要注意,这个思路与 reduce 思路不同之处在于,利用 reduce 的函数整体是个同步函数,自己先执行完毕构造 Promise 队列,然后在内存异步执行;而利用 async/await 的函数是利用将自己改造为一个异步函数,等待每一个 Promise 执行完毕。

  • 或使用第三方 async promise-fun 工具

# 参考

  • 用例仓库
  • MDN Array.prototype.reduce()
  • MDN Promise.all()
  • JavaScript 中数组方法 reduce 的妙用之处
  • 精读《用 Reduce 实现 Promise 串行执行》
编辑
#JavaScript
上次更新: 2024/04/15, 14:35:14
事件通信研究
记忆函数memoize

← 事件通信研究 记忆函数memoize→

最近更新
01
说说call、apply、bind是如何改变this的
03-29
02
从输入 URL 到页面加载完成发生了什么?
03-29
03
JavaScript进阶—— new 的执行过程
03-26
更多文章>
sunss | © 2020.08-2024.04 浙ICP备2022002957号-1
载入天数... 载入时分秒...  |  总访问量 次
提供CDN加速/云存储服务
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式