js中异步获取文件集并返回结果集

概述

最近在使用js中经常遇到需要去异步获取文件并对文件内容进行处理的需求。

详细的需求是:同时去异步获取多个文件,然后将多个文件的结果聚合起来,返回。

但是关于这样的做法有不少的实现方式,在这里主要做一个总结。

做法

我先在项目中放置了两个需要获取的json文件。

a.json

1
2
3
4
{
"a": "1",
"b": "2"
}

b.json

1
2
3
4
{
"c": "3",
"d": "4"
}

接下来开始获取这两个文件。

只使用 Promise/then

Promise/then介绍

Promise介绍

关于 Promise,可以看上面的链接,介绍的十分详细。

Promise有一个resolve()方法,将Promise对象的pending状态转化为resolved状态,并将操作的结果作为参数传递出去:例如 new Promise((resolve, reject) => { resolve('123') }),这里会将'123'作为Promise的结果传递出去。

接着可以用then方法去获取结果。then方法只能跟在Promise的后面,因此then方法的两个形参分别是Promise的resolve()方法传入的参数和reject()方法传入的参数。

一个小例子:

1
2
3
let promise = new Promise((resolve, reject) => { resolve('123') })
promise.then(data => console.log(data))
// 123

实现异步

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
let files = ['./data/a.json', './data/b.json'] // 放在相对于index.html目录下的两个json文件
let promises = files.map((file) => {
return new Promise((resolve, reject) => {
fetch(file).then((resp) => {
resolve(resp.json()) // resp.json() is a promise,resolve(resp.json())将包含的文件结果的promise传递出去
})
})
})
console.log('A : ', promises) // two promises
/*
A :
(2) [Promise, Promise]
0: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: Object
a: "1"
b: "2"
1: Promise {<resolved>: {…}}
length: 2
*/

// Promise.all()将promises数组转化为一个promise,并获取promises内部的promise的返回值,并将返回值以一个数组的形式传递出去
Promise.all(promises).then((results) => { console.log(results) })
/*
(2) [{…}, {…}]
0:
a: "1"
b: "2"
1:
c: "3"
d: "4"
length: 2
*/

使用async/await

使用async函数之前可能还需要了解一下Generator函数。

Generator函数介绍

Generator函数不同于普通的函数。它在函数名前面会有一个*的标志,而且内部可以使用yeild使得Generator函数具有一些状态,不会一次执行完毕。

Generator函数可以通过调用next()方法执行到下一个yeild,每次会返回一个包含{value: , done: }的对象,为yeild后面的表达式的结果。可以给next()函数传递值,使得这个值为上一个yeild表达式的返回结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function* genFunc() {
let resA = yield '1'
console.log(resA)
let resB = yield '2'
console.log(resB)
return '3'
}
// 此时还未执行gen()
let gen = genFunc()
// 开始执行第一个yield(yield '1')
console.log(gen.next())
// 执行第二个yeild(yield '2'),传入的参数为上一个yield的返回值,因此genFunc()中的resA='A'
console.log(gen.next('A'))
// 执行第三个yeild(return '3'),resB的值与上面同理
console.log(gen.next('B'))

结果:

async/await介绍

async函数介绍

async/await主要是将Generator函数的*替换为async,将yield替换为await,而且await后面需要跟promise,并且等待其执行完成,得到其执行结果;如果不是promise的话,会立即返回其值。另外async函数的返回值是一个promise

实现异步

1
2
3
4
5
6
7
8
9
let files = ['./data/a.json', './data/b.json']
async function fetchFilesData(files) {
let promises = files.map(async (file) => {
let resp = await fetch(file) // 此时await等待fetch的结果,但是这个过程是并发fetch
return resp.json()
})
return await Promise.all(promises) // 此时await等待并获取了Promise.all()的执行结果
}
fetchFilesData(files).then((results) => { console.log(results) }) // 最后这里还是用.then()去获取,不知道是否存在不用.then()的方法

结果:

使用axios替代fetch

听同学说用axios替代fetch会更好,经过查阅资料,主要是有axios从浏览器中创建 XMLHttpRequest,然而fetch是es规范中的实现方式,脱离了XMLHttpRequest,需要更多的配置。具体其他的区别需要在其他使用场景中去注意。

下面是两个方法的返回值的区别:

1
2
3
let files = ['./data/a.json', './data/b.json']
console.log('fetch:', fetch(files[0]))
console.log('axios:', axios.get(files[0]))

结果:

可以看到两者的区别。

实现异步

1
2
3
4
5
6
7
8
9
let files = ['./data/a.json', './data/b.json']
async function fetchFilesData(files) {
let promises = files.map((file) => {
return axios.get(file) // 得到一个返回response的promise
})
return await Promise.all(promises)
}
fetchFilesData(files).then((results) => { return results.map((result) => {return result.data}) })
.then((data) => {console.log(data)})

总结

对于不同实现方式的优点和缺点,以及原理尚还不是很清晰,特别是axios和fetch两者,还需要再多多学习。