224

I would like to use async/await with some filesystem operations. Normally async/await works fine because I use babel-plugin-syntax-async-functions.

But with this code I run into the if case where names is undefined:

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

When I rebuild the code into the callback hell version everything is OK and I get the filenames. Thanks for your hints.

Quellenangeber
  • 2,241
  • 2
  • 9
  • 3

12 Answers12

236

Native support for async/await fs functions since Node 11

Since Node.js 11.0.0 (stable), and 10.0.0 (experimental), you can access file system methods already promisify'd. Thanks to promises you can simply use try catch to handle exceptions instead of checking if the callback's returned value contains an error.

The API is very clean and elegant! Simply import file system methods from fs/promises instead of importing them directly from fs:

import fs from 'fs/promises'

async function listDir() {
  try {
    return await fs.readdir('path/to/dir')
  } catch (err) {
    console.error('Error occurred while reading directory:', err)
  }
}

listDir()
bman
  • 5,016
  • 4
  • 36
  • 69
179

Starting with node 8.0.0, you can use this:

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

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

SimplGy
  • 20,079
  • 15
  • 107
  • 144
Azbykov
  • 1,823
  • 1
  • 11
  • 5
  • 7
    In node v8.9.4, got a `SyntaxError: Unexpected token import` error message. does node8 supports `import` token by default? – makerj Jan 21 '18 at 10:26
  • 9
    @makerj he's using the new [`import`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) syntax. It currently requires some transpiling. Would be ok to also use `const fs = require('fs')` or `const { promisify } = require('util')` – Josh Sandlin Jan 26 '18 at 09:20
  • 2
    Noob question, but what's the `{err, names} = function` syntax called? – Qasim Mar 04 '18 at 16:56
  • 7
    @Qasim it is called destructuring assignment. – jaredkwright Mar 05 '18 at 18:27
  • @jaredkwright The destructuring call results in `Unexpected token =` using Node 10.1.0. – Alexander Zeitler May 10 '18 at 22:44
  • 1
    @AlexanderZeitler That may be true. I haven't looked to see if that is actually a correct use of destructuring. In the case of async await I think you would just do `names = await readdir('path/to/dir');` and if there is an `err` handle it in the `catch` block. Either way, the name of the syntax is destructuring assignment which was just in response to Qasim's question. – jaredkwright May 11 '18 at 15:17
  • This is so ugly, why do the only answers to this question throw errors on every version of node? Is it that hard to add an extra line to destructure longform? – Anthony Jun 21 '19 at 01:17
  • Why is it needed to declare the variable outside the try block, and not inside? – Firsh - justifiedgrid.com Jul 20 '22 at 14:21
108

Node.js 8.0.0

Native async / await

Promisify

From this version, you can use native Node.js function from util library.

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


Promise Wrapping

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

Advice

Always use try..catch for await blocks, if you don't want to rethrow exception upper.

dimpiax
  • 12,093
  • 5
  • 62
  • 45
  • This is strange. I am getting SyntaxError: await is only valid in async function... crying in rage. – Amiga500 Jul 20 '18 at 12:48
  • 2
    @VedranMaricevic. look at comments, `await` must be always in `async` block :) – dimpiax Jul 20 '18 at 14:20
  • @VedranMaricevic. You need to call that `const res = await readFile('data.json') console.log(res)` in some async function – Jayraj Feb 19 '19 at 07:58
  • promise wrapping `fs.promises` and using it with `async/await` is so confusing to me – oldboy Oct 28 '19 at 00:48
  • @PrimitiveNom Promise can be used in traditional way within `then`, `catch` etc. Where are async/await is modern behavior flow. – dimpiax Oct 28 '19 at 00:54
75

As of v10.0, you can use fs.Promises

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

Example using 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);
})();
KyleMit
  • 30,350
  • 66
  • 462
  • 664
Dan Starns
  • 3,765
  • 1
  • 10
  • 28
  • 1
    Works great, but important to note the open issue regarding the `ExperimentalWarning: The fs.promises API is experimental` warning: https://github.com/pnpm/pnpm/issues/1178 – DavidP Oct 12 '19 at 10:31
  • 2
    @DavidP what version of node are you using? 12 and above works fine – Dan Starns Oct 12 '19 at 10:58
  • 3
    Yes! Absolutely correct - I neglected to state version I am on: `v10.15.3` - it's possible to suppress the message. However, with the issue still open I thought it worth mentioning. – DavidP Oct 12 '19 at 11:07
  • 1
    @DavidP I mean it is worth a mention don't get me wrong, but node 12 is in LTS now so it's not a Biggie. – Dan Starns Oct 12 '19 at 11:52
  • how exactly do you use this with, say, `readFile`? im new to this whole promises thing, and all i want to do is have a function `getContent` that i can call and await in various parts throughout my script, yet this is proving very confusing – oldboy Oct 28 '19 at 01:22
  • @PrimitiveNom you could use [fs.promises.readFile](https://nodejs.org/dist/latest-v10.x/docs/api/fs.html#fs_fspromises_readfile_path_options) I have added a code snippet to my answer. – Dan Starns Oct 28 '19 at 07:35
  • 1
    In TypeScript (and modern JavaScript?) you can write `import { promises as fs } from "fs";`. – Jan Aagaard Dec 26 '20 at 19:45
  • @JanAagaard yeah, you can. There are 2 answers below with import. – Dan Starns Dec 26 '20 at 19:48
49

You might produce the wrong behavior because the File-Api fs.readdir does not return a promise. It only takes a callback. If you want to go with the async-await syntax you could 'promisify' the function like this:

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

and call it instead:

names = await readdirAsync('path/to/dir');
wursttheke
  • 491
  • 4
  • 3
  • I'm getting a weird response like this... Buffer(18524) [60, 115, 99, 114, 105, 112, 116, 32, 116, 110, 116, 45, 108, 105, 98, 62, 13, 10, 32, 32, 32, 32, 47, 42, 42, 13, 10, 32, 32, 32, 32, 32, 42, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 32, 63, 32, 50, 48, 50, 48, 32, 68, 101, 115, 105, 103, 110, 32, 65 …] – Paula Fleck Nov 27 '20 at 18:29
  • 1
    Use [`Buffer.toString`](https://masteringjs.io/tutorials/node/buffer-to-string) method. – Muhammed Bera Koç Mar 24 '21 at 19:29
20

Node v14.0.0 and above

you can just do:

import { readdir } from "fs/promises";

just like you would import from "fs"

see this PR for more details: https://github.com/nodejs/node/pull/31553

Konrad
  • 6,385
  • 12
  • 53
  • 96
19

This is the TypeScript version to the question. It is usable after 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
  • 3,111
  • 23
  • 34
8

I have this little helping module that exports promisified versions of fs functions

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

module.exports = {
  readdir: promisify(fs.readdir),
  readFile: promisify(fs.readFile),
  writeFile: promisify(fs.writeFile)
  // etc...
};
grigson
  • 3,458
  • 29
  • 20
5

Here is what worked for 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);
  }
})();

This code works in node 7.6 without babel when harmony flag is enabled: node --harmony my-script.js. And starting with node 7.7, you don't even need this flag!

The fsp library included in the beginning is just a promisified wrapper for fs (and fs-ext).

I’m really exited about what you can do in node without babel these days! Native async/await make writing code such a pleasure!

UPDATE 2017-06: fs-promise module was deprecated. Use fs-extra instead with the same API.

Alexander Kachkaev
  • 842
  • 15
  • 29
  • Downloading a library for this is pure overkill, dependency bloating is something that the community should be strongly against, infact a new npmjs should come into making that only has libs with 0 dependencies – PirateApp Dec 28 '17 at 07:12
5

Recommend using an npm package such as https://github.com/davetemplin/async-file, as compared to custom functions. For example:

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

Other answers are outdated

sean2078
  • 5,131
  • 6
  • 32
  • 32
0

You can use the simple and lightweight module https://github.com/nacholibre/nwc-l it supports both async and sync methods.

Note: this module was created by me.

nacholibre
  • 3,874
  • 3
  • 32
  • 35
0

you can use this code :

fs.promises.readdir(path)

// If promise resolved and
// data are fetched
.then(filenames => {
    for (let filename of filenames) {
        console.log(filename)
    }
})

// If promise is rejected
.catch(err => {
    console.log(err)
})