Utilizzo del filesystem in node.js con async / await


129

Vorrei usare async / await con alcune operazioni sul file system. Normalmente async / await funziona bene perché uso babel-plugin-syntax-async-functions.

Ma con questo codice mi imbatto nel caso in cui namesnon è definito:

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();

Quando ricostruisco il codice nella versione callback hell, tutto è OK e ottengo i nomi dei file. Grazie per i tuoi suggerimenti.

Risposte:


139

A partire dal nodo 8.0.0, puoi usare questo:

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();

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


7
Nel nodo v8.9.4, è stato visualizzato un SyntaxError: Unexpected token importmessaggio di errore. node8 supporta il importtoken per impostazione predefinita?
makerj

9
@makerj sta usando la nuova importsintassi. Attualmente richiede un po 'di transpiling. Va bene anche usare const fs = require('fs')oconst { promisify } = require('util')
Josh Sandlin

2
Domanda Noob, ma come si {err, names} = functionchiama la sintassi?
Qasim

6
@Qasim si chiama assegnazione destrutturante.
jaredkwright

1
@AlexanderZeitler Potrebbe essere vero. Non ho cercato di vedere se questo è effettivamente un uso corretto della destrutturazione. In caso di async, attendo penso che lo faresti names = await readdir('path/to/dir');e se c'è un errhandle nel catchblocco. In ogni caso, il nome della sintassi è l'assegnazione destrutturante che era solo in risposta alla domanda di Qasim.
jaredkwright

88

Il supporto nativo per async attende le funzioni fs dal nodo 11

A partire da Node.JS 11.0.0 (stabile) e versione 10.0.0 (sperimentale), hai accesso ai metodi del file system che sono già promessi e puoi usarli con la try catchgestione delle eccezioni piuttosto che controllare se il valore restituito dal callback contiene un errore.

L'API è molto pulita ed elegante! Usa semplicemente il .promisesmembro fsdell'oggetto:

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();

Questa API è stabile dalla versione 11.x secondo la documentazione del file system sul sito Node.js
TheHanna

1
@DanStarns se non fai la return awaittua promessa, il blocco delle catture non serve ... Penso che a volte è una buona pratica aspettare prima di tornare
538ROMEO

@ 538ROMEO ha appena esaminato questo e il tuo diritto. Grazie per segnalarlo.
DanStarns

Documentazione per questi metodi alternativi: nodejs.org/api/fs.html#fs_fs_promises_api
Jeevan Takhar

87

Node.js 8.0.0

Asincrono nativo / attendono

Promisify

Da questa versione è possibile utilizzare la funzione nativa Node.js dalla libreria 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()

Promessa di avvolgimento

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()

Consigli

Utilizzare sempre try..catchper i blocchi di attesa, se non si desidera rilanciare l'eccezione superiore.


Questo è strano. Ricevo SyntaxError: await è valido solo nella funzione async ... piangendo di rabbia.
Vedran Maricevic.

2
@VedranMaricevic. guarda i commenti, awaitdevono essere sempre in asyncblocco :)
dimpiax

@VedranMaricevic. Devi chiamarlo const res = await readFile('data.json') console.log(res)in una funzione asincrona
Jayraj

Promettere di avvolgerlo fs.promisese usarlo con async/awaitè così confuso per me
oldboy

@PrimitiveNom Promise può essere utilizzato in modo tradizionale all'interno then, catchecc. Dove sono asincrono / attendono è il flusso di comportamento moderno.
dimpiax

43

Potresti produrre un comportamento sbagliato perché File-Api fs.readdirnon restituisce una promessa. Ci vuole solo una richiamata. Se vuoi usare la sintassi async-await puoi 'promettere' la funzione in questo modo:

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

e chiamalo invece:

names = await readdirAsync('path/to/dir');

31

A partire dalla v10.0 , puoi usarefs.Promises

Esempio utilizzando 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();

Esempio utilizzando 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);
})();

Funziona alla grande, ma è importante notare il problema aperto relativo ExperimentalWarning: The fs.promises API is experimentalall'avviso: github.com/pnpm/pnpm/issues/1178
DavidP

1
@DavidP quale versione del nodo stai usando? 12 e oltre funziona bene
DanStarns

2
Sì! Assolutamente corretto - Ho trascurato di indicare la versione in cui mi trovo: v10.15.3- è possibile sopprimere il messaggio. Tuttavia, con il problema ancora aperto, ho pensato che valesse la pena menzionarlo.
DavidP

1
@DavidP Voglio dire che vale la pena menzionare, non fraintendetemi, ma il nodo 12 è in LTS ora, quindi non è un Biggie.
DanStarns

come lo usi esattamente con, diciamo readFile,? Sono nuovo a tutta questa cosa promette, e tutto ciò che voglio fare è avere una funzione getContentche posso chiamare e attendere in varie parti durante il mio script, ma questo si sta rivelando molto confuso
oldboy

8

Questa è la versione TypeScript della domanda. È utilizzabile dopo il nodo 11.0:

import { promises as fs } from 'fs';

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

5

Ecco cosa ha funzionato per me:

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);
  }
})();

Questo codice funziona nel nodo 7.6 senza babele quando bandiera armonia è attivata: node --harmony my-script.js. E a partire dal nodo 7.7, non hai nemmeno bisogno di questa bandiera !

La fsplibreria inclusa all'inizio è solo un wrapper promesso per fs(e fs-ext).

Sono davvero entusiasta di quello che puoi fare in node senza babel in questi giorni! Nativo async/ awaitrende la scrittura di codice un vero piacere!

AGGIORNAMENTO 2017-06: il modulo fs-promise è stato deprecato. Utilizzare fs-extrainvece con la stessa API.


Scaricare una libreria per questo è puro eccessivo, il gonfiore delle dipendenze è qualcosa contro cui la comunità dovrebbe essere fortemente contraria, infatti dovrebbe entrare in gioco un nuovo npmjs che ha solo librerie con 0 dipendenze
PirateApp

5

Consiglia di utilizzare un pacchetto npm come https://github.com/davetemplin/async-file , rispetto alle funzioni personalizzate. Per esempio:

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');

Altre risposte sono obsolete


5

Ho questo piccolo modulo di aiuto che esporta le versioni promesse di fsfunzioni

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

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.