繁体   English   中英

for循环中异步函数的顺序

[英]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的调用间隔更远,它们碰巧按顺序完成。


不能依赖这种行为。


如果您想让它们按顺序返回,则:

  1. 使用async关键字标记listAllJs
  2. 将您对fs.stat的调用fs.stat在一个返回 promise 的函数中 承诺应该解析为您想要的值,并且您不应该在回调中记录该值
  3. 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.existfs.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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM