Node.js 异步文件编程

问题描述

Q:使用异步接口,实现递归遍历查找指定文件

异步的优势:

  • 相较于同步接口,异步不阻塞主线程,速度更快。
  • 相较于await,也更快。尤其是只有一个任务在执行的时候,await虽是异步,但是同一个任务中,是按照指令顺序执行的。

设计解决思路

  • *异步的处理技巧是使用一个变量 asyncOps,标记当前正在进行的异步操作数量。**每次异步开始前,+1;结束后,-1。
  • *当一个异步操作完成后,需要检查是否所有异步都完成,完成则触发回调函数。**每次-1 后,检查 asyncOps 是否为 0,如果为 0,那么所有异步都完成,触发回调函数。

为了防止错误回调函数重复触发,使用一个变量进行标识。

代码地址

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// 递归查找文件
// 异步不阻塞主线程,速度更快
// 异步的处理技巧:使用一个变量asyncOps,标记当前正在进行的异步操作数量
// 每次异步开始前,+1;结束后,-1
// 每次-1后,检查asyncOps是否为0,如果为0,那么所有异步都完成,触发回调函数

const fs = require('fs')
const join = require('path').join

/**
*
* @param {RegExp} nameRe
* @param {String} rootPath
* @param {Function} callback
*/
function find(nameRe, rootPath, callback) {
const results = []
let asyncOps = 0
let errored = false

finder(rootPath)

/**
* 只触发一次
* @param {Error} err
*/
function handleError(err) {
if (errored) return
errored = true
callback(err)
}

/**
* @param {String} path
*/
function finder(path) {
++asyncOps

fs.readdir(path, (err, files) => {
if (err) return handleError(err)

for (const file of files) {
const fpath = join(path, file)

++asyncOps
fs.stat(fpath, (err, stats) => {
if (err) return handleError(err)

if (stats.isDirectory()) {
finder(fpath)
} else if (stats.isFile() && nameRe.test(file)) {
results.push(fpath)
}

--asyncOps
// 回调设计遵循:错误优先
if (!asyncOps) callback(null, results)
})
}

--asyncOps
if (!asyncOps) callback(null, results)
})
}
}

module.exports.find = find

使用效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const { find } = require('./async-find')
const path = require('path')

const rootPath = path.join(
__dirname,
'..',
'..',
'algorithm'
)

find(/better/, rootPath, (err, results) => {
if (err) throw err
console.log(results)
})