for循环内异步函数的序列

时间:2020-09-05 16:32:06

标签: javascript asynchronous

考虑以下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

我知道这种问题是没有用的,但是我正在学习异步功能,我真的很想知道其背后的原因。请解释一下吗?

3 个答案:

答案 0 :(得分:2)

无法保证fs.stat运行并触发回调函数将花费多长时间。

当您的循环仅调用它时,各种调用非常接近,并且fs.stat运行的时间段重叠。有时,一个较晚开始的日期可能会更快结束。

添加console.log语句时,会使每个循环花费的时间非常长

发生这种情况-在您的特定测试用例中,在计算机上,当计算机处于负载状态时,您品尝它-使完成回路的时间比所需的时间稍长fs.stat来获取数据。

由于对fs.stat的调用之间的距离较远,因此它们恰好按顺序完成。


不能依赖于此行为。


如果要让它们按顺序退回,则:

  1. the async keyword标记listAllJs
  2. Wrap your call to fs.stat in a function that returns a promise。 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)
    });
}

答案 1 :(得分:1)

“ stat”在后台运行,您的程序继续运行而无需等待其完成。因此,它的多个实例同时运行,使得无法确定哪个实例将首先打印输出。可能您的“ console.log(i)”花了很长时间才能确定哪个先完成。

答案 2 :(得分:0)

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'
]

很难调试承担太多职责的大型函数。分解问题可以使我们的程序更易于编写,并且我们的函数也可以高度重用。下一步将是在模块中定义一组功能。有关使用异步生成器的其他说明和其他方式,请参见this Q&A