为什么node.js`fs.existsSync`在包含在promise中时效果不好?

时间:2018-06-08 20:14:12

标签: javascript node.js asynchronous fs

我正在编写一个函数createFile来在目录中创建一个文件,除非它已经存在。我使用Node.js本机fs包来执行所有文件操作。我想让我的函数异步,所以我将所有fs函数包装在promises:

function writeFilePromise(writePath, textContent) {
    return new Promise((resolve, reject) => {
      fs.writeFile(writePath, textContent, (err) => {
        reject();
      });
      resolve();
    });
  }

  function mkDirPromise(dir) {
    return new Promise(((resolve, reject) => {
      fs.mkdir(path.join(constants.FILES_STORAGE_DIR, dir), (err) => {
        reject(err);
      });
      resolve();
    }));
  }

然后我想在promise中包装fs.existsSync以完成我的功能,但是包装它会导致偶然的错误行为,即,如果文件的目录不存在而我想创建一个目录将在没有文件的情况下创建为空。通过调试,我发现只有同步fs.existsSync才能始终。 这个功能代码:

function createFile(dir, fileName, httpMethod, textContent) {
    return new Promise(((resolve, reject) => {
      const searchPath = path.join(ROOT_DIR, dir, fileName);
      if (httpMethod === POST && fs.existsSync(searchPath)) {
        reject();
      } else {
        const fileExistsStatus = fs.existsSync(path.join(ROOT_DIR, dir));
        (async function fsOperations() {
          try {
            if (!fileExistsStatus) {
              await mkDirPromise(dir);
            }
            await writeFilePromise(searchPath, textContent);
            resolve();
          } catch (err) {
            reject(err);
          }
        }());
      }
    }));
  }

我缺少什么,如何将我的功能变为真正的异步?

3 个答案:

答案 0 :(得分:4)

  

偶尔会出现不正确的行为,即如果该文件的目录不存在而且我想创建一个目录,那么该目录将在没有该文件的情况下创建为空

这可能是由writeFilePromise的错误实施引起的,尤其是mkDirPromisefs.writeFilefs.mkdir是异步的,但是同步解析了promise。它应该是:

  function writeFilePromise(writePath, textContent) {
    return new Promise((resolve, reject) => {
      fs.writeFile(writePath, textContent, (err) => {
        if (err)
          reject(err);
        else
          resolve();
      });
    });
  }

  function mkDirPromise(dir) {
    return new Promise(((resolve, reject) => {
      fs.mkdir(path.join(constants.FILES_STORAGE_DIR, dir), (err) => {
        if (err)
          reject(err);
        else
          resolve();
      });
    }));
  }

这是util.promisify的用途:

const writeFilePromise = util.promisify(fs.writeFile);

即使这样,这也是一个轮子改造,因为已经存在第三方包,甚至更多,即fs-extra

createFile控制流量很差,并且使用了promise构造反模式。由于它使用async..await,因此应该是:

async function createFile(dir, fileName, httpMethod, textContent) {
  const searchPath = path.join(ROOT_DIR, dir, fileName);
  if (httpMethod === POST && fs.existsSync(searchPath)) {
    throw new Error();
  } else {
    const fileExistsStatus = fs.existsSync(path.join(ROOT_DIR, dir));
    if (!fileExistsStatus) {
      await mkDirPromise(dir);
    }
    await writeFilePromise(searchPath, textContent);
  }
}

应该提到existsSync是稳定的API方法,使用它来检查文件是否存在是可以接受的。正如the documentation所述,

  

请注意,不推荐使用fs.exists(),但不推荐使用fs.existsSync()。 (fs.exists()的回调参数接受与其他Node.js回调不一致的参数.fs.existsSync()不使用回调。)

答案 1 :(得分:2)

首先,您构建了writeFilePromisemkDirPromise,以便始终resolve,而不是reject。自fs.writeFile以来 并且fs.mkdir是异步的,一旦它们启动,线程就会立即转移到resolve()。我想你的意思是......

function writeFilePromise(writePath, textContent) {
    return new Promise((resolve, reject) => {
        fs.writeFile(writePath, textContent, (err) => {
            if (err) reject();
            else resolve();
        });
    });
}

function mkDirPromise(dir) {
    return new Promise((resolve, reject) => {
        fs.mkdir(path.join(constants.FILES_STORAGE_DIR, dir), (err) => {
            if (err) reject();
            else resolve();
        });
    });
}

fs.exists而言,已被删除,因此我不建议使用它。相反,请尝试fs.access

function accessPromise(dir) {
    return new Promise((resolve, reject) => {
        fs.access(dir, (err) => {
            if (err) reject();
            else resolve();
        });
    });
}

最后,尝试调整使用async函数声明的位置,以确保您正确地同步代码:

async function createFile(dir, fileName, httpMethod, textContent) {
    const searchPath = path.join(ROOT_DIR, dir, fileName);
    if (httpMethod === POST && await accessPromise(searchPath)) {
        return false;
    } else {
        const fileExistsStatus = await accessPromise(path.join(ROOT_DIR, dir));
        try {
            if (!fileExistsStatus) {
                await mkDirPromise(dir);
            }
            await writeFilePromise(searchPath, textContent);
            return true;
        } catch (err) {
            return false;
        }
    }
}

请记住在调用该函数时使用await createFile(dir, fileName, httpMethod, textContent)

答案 2 :(得分:1)

首先,考虑拒绝使用有意义的内容而不仅仅是INFO 2018-06-08 14:42:18 -0400 master-replica-0 Calling model_fn. INFO 2018-06-08 14:42:20 -0400 master-replica-0 Done calling model_fn. INFO 2018-06-08 14:42:20 -0400 master-replica-0 Create CheckpointSaverHook. INFO 2018-06-08 14:42:22 -0400 master-replica-0 Graph was finalized. INFO 2018-06-08 14:42:22 -0400 master-replica-0 Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA INFO 2018-06-08 14:42:22 -0400 master-replica-0 successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero INFO 2018-06-08 14:42:22 -0400 master-replica-0 Found device 0 with properties: ERROR 2018-06-08 14:42:22 -0400 master-replica-0 name: Tesla K80 major: 3 minor: 7 memoryClockRate(GHz): 0.8235 ERROR 2018-06-08 14:42:22 -0400 master-replica-0 pciBusID: 0000:00:04.0 ERROR 2018-06-08 14:42:22 -0400 master-replica-0 totalMemory: 11.17GiB freeMemory: 11.10GiB INFO 2018-06-08 14:42:22 -0400 master-replica-0 Adding visible gpu devices: 0 INFO 2018-06-08 14:42:23 -0400 master-replica-0 Device interconnect StreamExecutor with strength 1 edge matrix: INFO 2018-06-08 14:42:23 -0400 master-replica-0 0 INFO 2018-06-08 14:42:23 -0400 master-replica-0 0: N INFO 2018-06-08 14:42:23 -0400 master-replica-0 Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 10763 MB memory) -> physical GPU (device: 0, name: Tesla K80, p ci bus id: 0000:00:04.0, compute capability: 3.7) INFO 2018-06-08 14:42:23 -0400 master-replica-0 Running local_init_op. INFO 2018-06-08 14:42:23 -0400 master-replica-0 Done running local_init_op. INFO 2018-06-08 14:42:30 -0400 master-replica-0 Saving checkpoints for 1 into gs://teamcore-rnn/discrete_sales_test_0_less_ts/model.ckpt. INFO 2018-06-08 14:42:37 -0400 master-replica-0 Calling model_fn. INFO 2018-06-08 14:42:39 -0400 master-replica-0 Done calling model_fn. INFO 2018-06-08 14:42:39 -0400 master-replica-0 Starting evaluation at 2018-06-08-18:42:39 INFO 2018-06-08 14:42:39 -0400 master-replica-0 Graph was finalized. INFO 2018-06-08 14:42:39 -0400 master-replica-0 Adding visible gpu devices: 0 INFO 2018-06-08 14:42:39 -0400 master-replica-0 Restoring parameters from gs://teamcore-rnn/discrete_sales_test_0_less_ts/model.ckpt-1 INFO 2018-06-08 14:42:39 -0400 master-replica-0 Device interconnect StreamExecutor with strength 1 edge matrix: INFO 2018-06-08 14:42:39 -0400 master-replica-0 0 INFO 2018-06-08 14:42:39 -0400 master-replica-0 0: N INFO 2018-06-08 14:42:39 -0400 master-replica-0 Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 10763 MB memory) -> physical GPU (device: 0, name: Tesla K80, p ci bus id: 0000:00:04.0, compute capability: 3.7) INFO 2018-06-08 14:42:39 -0400 master-replica-0 Running local_init_op. INFO 2018-06-08 14:42:39 -0400 master-replica-0 Done running local_init_op.

由于您正在考虑使用异步和承诺,因此我不建议使用def tf_record_input_fn(filenames, parser_fn, params, mode=tf.estimator.ModeKeys.TRAIN): epochs = params.epochs if mode == tf.estimator.ModeKeys.TRAIN else 1 dataset = tf.data.TFRecordDataset(filenames) dataset = dataset.map(parser_fn).filter(filter_empty_labels) dataset = dataset.filter(filter_time_series_with_nans) dataset = dataset.shuffle(params.batch_size * 10).repeat(epochs).batch( params.batch_size) iterator = dataset.make_one_shot_iterator() ts_features_batch, c_features_batch, key_batch, labels_batch = iterator.get_next( ) features_batch = { 'time_series': ts_features_batch, 'context': c_features_batch, 'key': key_batch } return features_batch, labels_batch 函数。此外,我们已弃用reject(),请尝试使用fs.xxxSync()

我想你只会在HTTP方法是POST的情况下创建文件,但是当当前的if-else逻辑中HTTP方法不是POST时,将始终创建文件。

无需创建立即调用的异步函数。

试试这个:

fs.exists