From callbacks to fs/promises to handle the file system in Node.js

mrm8488 profile image Manuel Romero Updated on ・2 min read

In this post, I will show you the evolution of Node.js to work with the file system.
Let's start by creating a file:

const fs = require("fs");

fs.writeFile("/tmp/test.js", "console.log('Hello world');", error => {
    if (error) console.error(error);
    else console.log("file created successfully!");

If we wanted to avoid callbacks, before Node.js v8 we had to manually promisify the fs.writeFile function or using third party modules such as bluebird or Q.

Let's promisify manually and wrap in a function the above code:

const fs = require("fs");

const writeFilePromise = (file, data) => {
    return new Promise((resolve, reject) => {
        fs.writeFile(file, data, error => {
            if (error) reject(error);
            resolve("file created successfully with handcrafted Promise!");

        "console.log('Hello world with handcrafted promise!');"
    .then(result => console.log(result))

    .catch(error => console.log(error));

With the arrival of Node.js V8, 'util.promisify()' alllowed us to convert I/O functions that return callbacks into I/O functions that return promises.

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

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

writeFile("/tmp/test3.js", "console.log('Hello world with promisify!');")
  .then(() => console.log("file created successfully with promisify!"))

  .catch(error => console.log(error));

Let's see the async/await version:

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

const writeFile = promisify(fs.writeFile);

async function main() {
    await writeFile("/tmp/test4.js",
        "console.log('Hello world with promisify and async/await!');");

    console.info("file created successfully with promisify and async/await!");

main().catch(error => console.error(error));

In its latest release (Node.js V10), functions of the fs can return promises directly, eliminating the extra step and overhead of the old way. This due to its fs/promises API.

const fsp = require("fs/promises");

try {
    await fsp.writeFile("/tmp/test5.js", "console.log('Hello world with Node.js v10 fs/promises!'");
    console.info("File created successfully with Node.js v10 fs/promises!");
} catch (error){

Note I am using top level await (my await code is not inside and async function). This feature is experimental yet, so if you want to check it out, use the following flag: -- experimental-repl-await.

The fs/promises API provides the following methods:

access, copyFile, open, read, write, rename, truncate, ftruncate, rmdir, fdatasync, fsync, mkdir, readdir, readlink, symlink, fstat, lstat, stat, link, unlink, fchmod, chmod, lchmod, lchown, fchown, chown, utimes, futimes, realpath, mkdtemp, writeFile, appendFile and readFile.


In Node 13 it works even with ESM modules:

import { promises as fs } from 'fs';

try {
    await fs.writeFile("/tmp/test6.js", "console.log('Hello world with Node.js v13 fs.promises!'");
    console.info("File created successfully with Node.js v13 fs.promises!");
} catch (error){


markdown guide

It looks like "fs.writeFile(tempFile, response.auioContent, 'binary')" might return a promise now without using an additional library...


const fsp = require('fs').promises; // Marked experimental !!!
const fsp = require("fs/promises"); // XXX Invalid