Korzystanie z systemu plików w node.js z async / await


130

Chciałbym używać async / await z niektórymi operacjami na systemie plików. Zwykle async / await działa dobrze, ponieważ używam babel-plugin-syntax-async-functions.

Ale z tym kodem napotykam przypadek if, w którym namesjest niezdefiniowany:

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

Kiedy przebudowuję kod na wersję callback hell, wszystko jest w porządku i otrzymuję nazwy plików. Dzięki za podpowiedzi.

Odpowiedzi:


140

Począwszy od węzła 8.0.0, możesz użyć tego:

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

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


7
W węźle w wersji 8.9.4 pojawił się SyntaxError: Unexpected token importkomunikat o błędzie. czy node8 domyślnie obsługuje importtoken?
makerj

9
@makerj używa nowej importskładni. Obecnie wymaga transpilacji. Byłoby w porządku, aby również użyć const fs = require('fs')lubconst { promisify } = require('util')
Josh Sandlin

2
Noob pytanie, ale jak {err, names} = functionnazywa się składnia?
Qasim,

6
@Qasim nazywa się to przydziałem destrukturyzacji.
jaredkwright

1
@AlexanderZeitler To może być prawda. Nie sprawdzałem, czy to właściwie jest właściwe użycie destrukturyzacji. W przypadku oczekiwania async myślę, że po prostu zrobiłbyś names = await readdir('path/to/dir');i jeśli jest erruchwyt w catchbloku. Tak czy inaczej, nazwa składni to destrukturyzujące przypisanie, które było właśnie odpowiedzią na pytanie Qasima.
jaredkwright

88

Natywne wsparcie dla funkcji async await fs od Node 11

Od wersji Node.JS 11.0.0 (stabilna) i 10.0.0 (eksperymentalna) masz dostęp do metod systemu plików, które są już obiecane i możesz ich używać z try catchobsługą wyjątków zamiast sprawdzać, czy zwrócona wartość wywołania zwrotnego zawiera błąd.

Interfejs API jest bardzo czysty i elegancki! Po prostu użyj .promisesczłonka fsobiektu:

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

Ten interfejs API jest stabilny od wersji 11.x zgodnie z dokumentacją systemu plików w witrynie Node.js
TheHanna

1
@DanStarns, jeśli nie return awaitobiecasz, blokada na nic się nie przyda ... Myślę, że czasami dobrą praktyką jest
zaczekanie

@ 538ROMEO właśnie zajrzał do tego i po prawej stronie. Dzięki za wskazanie tego.
DanStarns

Dokumentacja alternatywnych metod: nodejs.org/api/fs.html#fs_fs_promises_api
Jeevan Takhar

87

Node.js 8.0.0

Natywny async / await

Promisify

Od tej wersji można używać natywnej funkcji Node.js z biblioteki 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()

Pakowanie obietnic

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

Rada

Zawsze używaj try..catchdla bloków await, jeśli nie chcesz ponownie wyrzucać wyjątku górnego.


To jest dziwne. Otrzymuję błąd SyntaxError: await działa tylko w funkcji async ... płacze z wściekłości.
Vedran Maricevic.

2
@VedranMaricevic. spójrz na komentarze, awaitzawsze musi być w asyncbloku :)
dimpiax

@VedranMaricevic. Musisz to wywołać const res = await readFile('data.json') console.log(res)w jakiejś funkcji asynchronicznej
Jayraj,

Obietnica zawijania fs.promisesi używania jej async/awaitjest dla mnie tak myląca
oldboy

@PrimitiveNom Promesa może być stosowany w tradycyjny sposób wewnątrz then, catchitd. Gdzie są asynchroniczne / Oczekujcie jest nowoczesny przepływ zachowanie.
dimpiax

43

Możesz uzyskać niewłaściwe zachowanie, ponieważ File-Api fs.readdirnie zwraca obietnicy. Wystarczy oddzwonić. Jeśli chcesz korzystać ze składni async-await, możesz `` obiecać '' funkcję w następujący sposób:

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

i nazwij to zamiast tego:

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

32

Od wersji 10.0 możesz używaćfs.Promises

Przykład użycia 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();

Przykład użycia 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);
})();

1
Działa świetnie, ale ważne jest, aby zwrócić uwagę na otwarty problem dotyczący ExperimentalWarning: The fs.promises API is experimentalostrzeżenia: github.com/pnpm/pnpm/issues/1178
DavidP,

1
@DavidP jakiej wersji węzła używasz? 12 i
nowsze

2
Tak! Jak najbardziej poprawne - zaniedbałem podanie wersji na której jestem: v10.15.3- można ukryć komunikat. Jednak mając wciąż otwarty problem, pomyślałem, że warto o nim wspomnieć.
DavidP

1
@DavidP Chodzi mi o to, że warto wspomnieć, nie zrozum mnie źle, ale węzeł 12 jest teraz w LTS, więc nie jest to Biggie.
DanStarns,

jak dokładnie tego używasz, powiedzmy readFile? Jestem nowy w tej całej sprawie obietnic i wszystko, czego chcę, to mieć funkcję, getContentktórą mogę wywołać i czekać w różnych częściach mojego skryptu, ale to okazuje się bardzo zagmatwane
oldboy

8

To jest wersja TypeScript pytania. Można go używać po Węzeł 11.0:

import { promises as fs } from 'fs';

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

5

Oto, co zadziałało dla mnie:

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

Ten kod działa w węźle 7.6 bez babel gdy flaga harmonia jest włączona: node --harmony my-script.js. Począwszy od węzła 7.7, nie potrzebujesz nawet tej flagi !

fspBiblioteka zawarte na początku jest tylko promisified wrapper dla fs(i fs-ext).

Jestem naprawdę podekscytowany tym, co możesz teraz zrobić w Node bez Babel! Native async/ awaitsprawiają, że pisanie kodu to taka przyjemność!

UPDATE 2017-06: moduł fs-promise został wycofany. fs-extraZamiast tego użyj tego samego interfejsu API.


Pobieranie biblioteki w tym celu jest czystą przesadą, nadęty zależności to coś, czemu społeczność powinna być zdecydowanie przeciw, w rzeczywistości nowy npmj powinien pojawić się, który ma tylko biblioteki z
zerowymi

5

Zaleca się używanie pakietu npm, takiego jak https://github.com/davetemplin/async-file , w porównaniu z funkcjami niestandardowymi. Na przykład:

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

Inne odpowiedzi są nieaktualne


5

Mam ten mały moduł pomocniczy, który eksportuje obiecane wersje fsfunkcji

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

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.