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) (源数组)
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
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: '上海' }
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
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.all
是并行执行其接收到的promise
队列- 函数执行结果的响应先后不影响队列的排列
- 即使只有一个失败,也会返回整个失败,检测到失败立即判定为失败
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
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
})
2
3
4
5
6
7
8
9
10
11
12
耗时为总计时间 6000ms,运行效果图如下:
小结:
- promise函数是在执行时调用,和
Prommise.all
有所区别 reduce
初始默认值指定Promise.resolve()
状态,确保开始执行.then
的状态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()
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
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 工具