考虑以下for循环
import * as fs from 'fs'
function listAllJs() {
let files = [ 'abc.js', 'bcd.js', 'e', 'main.js', 'maincopy.ts', 'mainfixedbug.ts', 'package-lock.json', 'package.json' ]
for (let i = 0; i<files.length; i++) {
let file = files[i]
fs.stat(file, (err, stat) => {
console.log(i, file)
});
}
}
listAllJs()
终端将打印出
1 bcd.js
0 abc.js
4 maincopy.ts
2 e
3 main.js
6 package-lock.json
5 mainfixedbug.ts
7 package.json
或
0 abc.js
3 main.js
2 e
1 bcd.js
4 maincopy.ts
5 mainfixedbug.ts
6 package-lock.json
7 package.json
或其他可能的组合,例如1 3 2 4 6 7 5
索引不是按升序排列的,终端将打印出不同的顺序。
但是当我在异步功能之前添加console.log(i)
function listAllJs() {
let files = [ 'abc.js', 'bcd.js', 'e', 'main.js', 'maincopy.ts', 'mainfixedbug.ts', 'package-lock.json', 'package.json' ]
for (let i = 0; i<files.length; i++) {
let file = files[i]
console.log(i)
fs.stat(file, (err, stat) => {
console.log(i, file)
});
}
}
列表将始终按升序列出。
0
1
2
3
4
5
6
7
0 abc.js
1 bcd.js
2 e
3 main.js
4 maincopy.ts
5 mainfixedbug.ts
6 package-lock.json
7 package.json
我知道这种问题是没有用的,但是我正在学习异步功能,我真的很想知道其背后的原因。请解释一下吗?
答案 0 :(得分:2)
无法保证fs.stat
运行并触发回调函数将花费多长时间。
当您的循环仅调用它时,各种调用非常接近,并且fs.stat
运行的时间段重叠。有时,一个较晚开始的日期可能会更快结束。
添加console.log
语句时,会使每个循环花费的时间非常长。
发生这种情况-在您的特定测试用例中,在计算机上,当计算机处于负载状态时,您品尝它-使完成回路的时间比所需的时间稍长fs.stat
来获取数据。
由于对fs.stat
的调用之间的距离较远,因此它们恰好按顺序完成。
您不能依赖于此行为。
如果要让它们按顺序退回,则:
async
keyword标记listAllJs
。fs.stat
in a function that returns a promise。 Promise应该解析为所需的值,并且您不应该在回调中记录该值await
诺言并收集其返回值并将其记录。这样
function getStat(file) {
return new Promise( (res, rej) => {
fs.stat(file, (err, stat) => {
res(file);
});
};
}
function listAllJs() {
let files = [ 'abc.js', 'bcd.js', 'e', 'main.js', 'maincopy.ts', 'mainfixedbug.ts', 'package-lock.json', 'package.json' ]
for (let i = 0; i<files.length; i++) {
let file = files[i];
const stat = await getStat(file);
console.log(i, stat)
}
}
或者,为了更快,让对fs.stat
的调用并行运行,并将promise存储在数组中以保持顺序。使用Promise.all读取结果,一目了然。
function listAllJs() {
let files = [ 'abc.js', 'bcd.js', 'e', 'main.js', 'maincopy.ts', 'mainfixedbug.ts', 'package-lock.json', 'package.json' ];
const promises = [[];
for (let i = 0; i<files.length; i++) {
let file = files[i];
const stat = getStat(file);
promises.push(stat);
}
const stats = await Promise.all(promises);
stats.forEach( (stat, index) {
console.log(index, stat)
});
}
答案 1 :(得分:1)
“ stat”在后台运行,您的程序继续运行而无需等待其完成。因此,它的多个实例同时运行,使得无法确定哪个实例将首先打印输出。可能您的“ console.log(i)”花了很长时间才能确定哪个先完成。
答案 2 :(得分:0)
fs / promises和fs.Dirent
这是一个使用Node的快速fs.Dirent对象和fs/promises模块的高效,无阻塞的ls
程序。这种方法使您可以跳过每条路径上浪费的fs.exist
或fs.stat
调用-
// main.js
import { readdir } from "fs/promises"
import { join } from "path"
async function* ls (path = ".")
{ yield path
for (const dirent of await readdir(path, { withFileTypes: true }))
if (dirent.isDirectory())
yield* ls(join(path, dirent.name))
else
yield join(path, dirent.name)
}
async function* empty () {}
async function toArray (iter = empty())
{ let r = []
for await (const x of iter)
r.push(x)
return r
}
toArray(ls(".")).then(console.log, console.error)
让我们获取一些示例文件,以便我们看到ls
正常工作-
$ yarn add immutable # (just some example package)
$ node main.js
[
'.',
'main.js',
'node_modules',
'node_modules/.yarn-integrity',
'node_modules/immutable',
'node_modules/immutable/LICENSE',
'node_modules/immutable/README.md',
'node_modules/immutable/contrib',
'node_modules/immutable/contrib/cursor',
'node_modules/immutable/contrib/cursor/README.md',
'node_modules/immutable/contrib/cursor/__tests__',
'node_modules/immutable/contrib/cursor/__tests__/Cursor.ts.skip',
'node_modules/immutable/contrib/cursor/index.d.ts',
'node_modules/immutable/contrib/cursor/index.js',
'node_modules/immutable/dist',
'node_modules/immutable/dist/immutable-nonambient.d.ts',
'node_modules/immutable/dist/immutable.d.ts',
'node_modules/immutable/dist/immutable.es.js',
'node_modules/immutable/dist/immutable.js',
'node_modules/immutable/dist/immutable.js.flow',
'node_modules/immutable/dist/immutable.min.js',
'node_modules/immutable/package.json',
'package.json',
'yarn.lock'
]
我们只想filter
个文件.js
-
import { extname } from "path"
async function* filter(iter = empty(), test = x => x)
{ for await (const x of iter)
if (Boolean(test(x)))
yield x
}
const lsJs = (path = ".") =>
filter // <- filtered stream
( ls(path) // <- input stream
, p => extname(p) === ".js" // <- filter predicate
)
toArray(lsJs(".")).then(console.log, console.error)
// => ...
[
'main.js',
'node_modules/immutable/contrib/cursor/index.js',
'node_modules/immutable/dist/immutable.es.js',
'node_modules/immutable/dist/immutable.js',
'node_modules/immutable/dist/immutable.min.js'
]
更通用的lsExt
允许我们按任何扩展名进行过滤。我们不仅限于.js
-
const lsExt = (path = ".", ext) =>
ext
? filter(ls(path), p => extname(p) === ext)
: ls(path)
toArray(lsExt(".", ".json")).then(console.log, console.error)
// => ...
[
'node_modules/immutable/package.json',
'package.json'
]
很难调试承担太多职责的大型函数。分解问题可以使我们的程序更易于编写,并且我们的函数也可以高度重用。下一步将是在模块中定义一组功能。有关使用异步生成器的其他说明和其他方式,请参见this Q&A。