Utilisation du système de fichiers dans node.js avec async / await

129

Je voudrais utiliser async / await avec certaines opérations du système de fichiers. Normalement, async / await fonctionne bien parce que j'utilise babel-plugin-syntax-async-functions.

Mais avec ce code, je rencontre le cas if où namesn'est pas défini:

import fs from 'fs';

async function myF() {
  let names;
  try {
    names = await fs.readdir('path/to/dir');
  } catch (e) {
    console.log('e', e);
  }
  if (names === undefined) {
    console.log('undefined');
  } else {
    console.log('First Name', names[0]);
  }
}

myF();

Lorsque je reconstruis le code dans la version de l'enfer de rappel, tout va bien et j'obtiens les noms de fichiers. Merci pour vos conseils.

Quellenangeber
la source

Réponses:

139

À partir du nœud 8.0.0, vous pouvez utiliser ceci:

const fs = require('fs');
const util = require('util');

const readdir = util.promisify(fs.readdir);

async function myF() {
  let names;
  try {
    names = await readdir('path/to/dir');
  } catch (err) {
    console.log(err);
  }
  if (names === undefined) {
    console.log('undefined');
  } else {
    console.log('First Name', names[0]);
  }
}

myF();

Voir https://nodejs.org/dist/latest-v8.x/docs/api/util.html#util_util_promisify_original

Azbykov
la source
7
Dans le nœud v8.9.4, un SyntaxError: Unexpected token importmessage d'erreur s'affiche. node8 prend-il en charge le importjeton par défaut?
makerj
9
@makerj il utilise la nouvelle importsyntaxe. Il nécessite actuellement un certain transpilage. Serait bien d'utiliser également const fs = require('fs')ouconst { promisify } = require('util')
Josh Sandlin
2
Noob question, mais comment {err, names} = functions'appelle la syntaxe?
Qasim
6
@Qasim c'est ce qu'on appelle l'affectation de déstructuration.
jaredkwright
1
@AlexanderZeitler C'est peut-être vrai. Je n'ai pas cherché à voir si c'était réellement une utilisation correcte de la déstructuration. Dans le cas de l'attente asynchrone, je pense que vous feriez juste names = await readdir('path/to/dir');et s'il y a une errpoignée dans le catchbloc. Quoi qu'il en soit, le nom de la syntaxe est une affectation de déstructuration qui était juste en réponse à la question de Qasim.
jaredkwright
88

Prise en charge native des fonctions async await fs depuis Node 11

Depuis Node.JS 11.0.0 (stable) et la version 10.0.0 (expérimentale), vous avez accès aux méthodes du système de fichiers qui sont déjà promis et vous pouvez les utiliser avec la try catchgestion des exceptions plutôt que de vérifier si la valeur renvoyée par le rappel contient une erreur.

L'API est très propre et élégante! Utilisez simplement le .promisesmembre de l' fsobjet:

import fs from 'fs';
const fsPromises = fs.promises;

async function listDir() {
  try {
    return fsPromises.readdir('path/to/dir');
  } catch (err) {
    console.error('Error occured while reading directory!', err);
  }
}

listDir();
bman
la source
Cette API est stable à partir de la version 11.x selon la documentation du système de fichiers sur le site Node.js
TheHanna
1
@DanStarns si vous ne tenez pas return awaitvotre promesse, le bloc catch ne sert à rien ... Je pense que c'est parfois une bonne pratique d'attendre avant de revenir
538ROMEO
@ 538ROMEO vient de regarder ceci et votre droite. Merci de l'avoir signalé.
DanStarns
Documentation pour ces méthodes alternatives: nodejs.org/api/fs.html#fs_fs_promises_api
Jeevan Takhar
87

Node.js 8.0.0

Asynchrone natif / attente

Promisify

À partir de cette version, vous pouvez utiliser la fonction native Node.js de la bibliothèque util .

const fs = require('fs')
const { promisify } = require('util')

const readFileAsync = promisify(fs.readFile)
const writeFileAsync = promisify(fs.writeFile)

const run = async () => {
  const res = await readFileAsync('./data.json')
  console.log(res)
}

run()


Emballage de la promesse

const fs = require('fs')

const readFile = (path, opts = 'utf8') =>
  new Promise((resolve, reject) => {
    fs.readFile(path, opts, (err, data) => {
      if (err) reject(err)
      else resolve(data)
    })
  })

const writeFile = (path, data, opts = 'utf8') =>
  new Promise((resolve, reject) => {
    fs.writeFile(path, data, opts, (err) => {
      if (err) reject(err)
      else resolve()
    })
  })

module.exports = {
  readFile,
  writeFile
}

...


// in some file, with imported functions above
// in async block
const run = async () => {
  const res = await readFile('./data.json')
  console.log(res)
}

run()

Conseil

Utilisez toujours try..catchpour les blocs d'attente, si vous ne voulez pas renvoyer l'exception en haut.

dimpiax
la source
Cela est étrange. Je reçois SyntaxError: await n'est valide que dans la fonction async ... pleurer de rage.
Vedran Maricevic.
2
@VedranMaricevic. regardez les commentaires, awaitdoit toujours être dans le asyncbloc :)
dimpiax
@VedranMaricevic. Vous devez appeler cela const res = await readFile('data.json') console.log(res)dans une fonction asynchrone
Jayraj
promettre l'emballage fs.promiseset l'utiliser avec async/awaitest si déroutant pour moi
oldboy
@PrimitiveNom Promise peut être utilisé de manière traditionnelle à l'intérieur then, catchetc. Où sont async / await est le flux de comportement moderne.
dimpiax
43

Vous risquez de produire un comportement incorrect car File-Api fs.readdirne renvoie pas de promesse. Cela ne prend qu'un rappel. Si vous voulez utiliser la syntaxe async-await, vous pouvez `` promettre '' la fonction comme suit:

function readdirAsync(path) {
  return new Promise(function (resolve, reject) {
    fs.readdir(path, function (error, result) {
      if (error) {
        reject(error);
      } else {
        resolve(result);
      }
    });
  });
}

et appelez-le à la place:

names = await readdirAsync('path/to/dir');
Wursttheke
la source
31

À partir de la v10.0 , vous pouvez utiliserfs.Promises

Exemple d'utilisation readdir

const { promises: fs } = require("fs");

async function myF() {
    let names;
    try {
        names = await fs.readdir("path/to/dir");
    } catch (e) {
        console.log("e", e);
    }
    if (names === undefined) {
        console.log("undefined");
    } else {
        console.log("First Name", names[0]);
    }
}

myF();

Exemple d'utilisation readFile

const { promises: fs } = require("fs");

async function getContent(filePath, encoding = "utf-8") {
    if (!filePath) {
        throw new Error("filePath required");
    }

    return fs.readFile(filePath, { encoding });
}

(async () => {
    const content = await getContent("./package.json");

    console.log(content);
})();
DanStarns
la source
Fonctionne très bien, mais il est important de noter le problème ouvert concernant l' ExperimentalWarning: The fs.promises API is experimentalavertissement: github.com/pnpm/pnpm/issues/1178
DavidP
1
@DavidP quelle version de nœud utilisez-vous? 12 et plus fonctionne bien
DanStarns
2
Oui! Absolument correct - j'ai négligé d'indiquer la version sur laquelle je suis: v10.15.3- il est possible de supprimer le message. Cependant, avec le problème toujours ouvert, j'ai pensé qu'il valait la peine d'être mentionné.
DavidP
1
@DavidP Je veux dire que cela vaut la peine d'être mentionné, ne vous méprenez pas, mais le nœud 12 est maintenant dans LTS, donc ce n'est pas un Biggie.
DanStarns
comment utilisez-vous exactement cela avec, disons readFile,? Je suis nouveau dans tout ce truc de promesses, et tout ce que je veux faire, c'est avoir une fonction getContentque je peux appeler et attendre dans diverses parties de mon script, mais cela s'avère très déroutant
oldboy
8

Ceci est la version TypeScript à la question. Il est utilisable après le Node 11.0:

import { promises as fs } from 'fs';

async function loadMonoCounter() {
    const data = await fs.readFile('monolitic.txt', 'binary');
    return Buffer.from(data);
}
HKTonyLee
la source
5

Voici ce qui a fonctionné pour moi:

const fsp = require('fs-promise');

(async () => {
  try {
    const names = await fsp.readdir('path/to/dir');
    console.log(names[0]);
  } catch (e) {
    console.log('error: ', e);
  }
})();

Ce code fonctionne dans le nœud 7.6 sans babel lorsque le drapeau de l' harmonie est activée: node --harmony my-script.js. Et à partir du nœud 7.7, vous n'avez même pas besoin de ce drapeau !

La fspbibliothèque incluse au début n'est qu'un wrapper promis pour fs(et fs-ext).

Je suis vraiment ravi de ce que vous pouvez faire dans node sans babel ces jours-ci! Natif async/ awaitfaire de l'écriture du code un tel plaisir!

MISE À JOUR 2017-06: le module fs-promise est obsolète. Utilisez à la fs-extraplace avec la même API.

Alexandre Kachkaev
la source
Télécharger une bibliothèque pour cela est purement excessif, les ballonnements de dépendance sont quelque chose contre lequel la communauté devrait être fermement opposée, en fait un nouveau npmjs devrait être créé qui n'a que des bibliothèques avec 0 dépendances
PirateApp
5

Il est recommandé d'utiliser un package npm tel que https://github.com/davetemplin/async-file , par rapport aux fonctions personnalisées. Par exemple:

import * as fs from 'async-file';

await fs.rename('/tmp/hello', '/tmp/world');
await fs.appendFile('message.txt', 'data to append');
await fs.access('/etc/passd', fs.constants.R_OK | fs.constants.W_OK);

var stats = await fs.stat('/tmp/hello', '/tmp/world');

D'autres réponses sont obsolètes

sean2078
la source
5

J'ai ce petit module d'aide qui exporte des versions promises des fsfonctions

const fs = require("fs");
const {promisify} = require("util")

module.exports = {
  readdir: promisify(fs.readdir),
  readFile: promisify(fs.readFile),
  writeFile: promisify(fs.writeFile)
  // etc...
};
Grigson
la source
1

Node v14.0.0 et supérieur

vous pouvez simplement faire:

import { readdir } from "fs/promises";

tout comme vous importeriez de "fs"

voir ce PR pour plus de détails: https://github.com/nodejs/node/pull/31553

Konrad
la source