[英]The sequence of async function inside for loop
考虑以下 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
我知道这种问题毫无用处,但我正在学习异步函数,我真的很想知道它背后的原因。 请问有什么解释吗?
无法保证fs.stat
运行和触发回调函数需要多长时间。
当您的循环只调用它时,各种调用非常接近,并且fs.stat
运行的时间段重叠。 有时,晚开始的会早点结束。
当您添加console.log
语句时,您会使每个循环花费的时间稍微长一些。
发生这种情况 - 在您的特定测试用例中,在您的计算机上,当您的计算机处于负载下时,您正在品尝它 - 使完成循环所需的时间比fs.stat
所需的时间fs.stat
获取数据。
由于对fs.stat
的调用间隔更远,它们碰巧按顺序完成。
你不能依赖这种行为。
如果您想让它们按顺序返回,则:
async
关键字标记listAllJs
。fs.stat
的调用fs.stat
在一个返回 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)
});
}
“stat”在后台运行,您的程序将继续运行,无需等待它完成。 因此,它的多个实例同时运行,从而无法预测哪个将首先打印输出。 可能您的“console.log(i)”需要足够长的时间来确定哪个先完成。
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'
]
很难调试承担太多责任的大函数。 分解问题使我们的程序更容易编写,我们的函数也具有高度的可重用性。 下一步是在模块中定义我们的功能集。 有关利用异步生成器的附加说明和其他方法,请参阅此问答。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.