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.

Quellenangeber
źródło

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

Azbykov
źródło
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();
bman
źródło
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.

dimpiax
źródło
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');
wursttheke
źródło
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);
})();
DanStarns
źródło
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);
}
HKTonyLee
źródło
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.

Alexander Kachkaev
źródło
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

sean2078
źródło
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...
};
Grigson
źródło
1

Węzeł 14.0.0 i nowsze

możesz po prostu zrobić:

import { readdir } from "fs/promises";

tak jak importujesz z "fs"

zobacz ten PR, aby uzyskać więcej informacji: https://github.com/nodejs/node/pull/31553

Konrad
źródło