callbackからPromiseへ
次のコードはtest.txtというファイルにHello!と書き込んだあと、その内容を読み込んで表示するプログラムです。
const fs = require('fs');
fs.writeFile('test.txt', 'Hello!', (err) => {
    if (err) {
        console.log(err.toString());
        return;
    }
    console.log('test.txtが作成されました');
    fs.readFile('test.txt', 'utf-8', (err, content) => {
        if (err) {
            console.log(err.toString());
            return;
        }
        console.log(content);
    });
});
読み込みは書き込みが済んでからする必要があるので、 fs.readFile は fs.writeFile のコールバック中にあります。さらに読み込みの後で表示を行うので、 console.log(content) は fs.readFile のコールバックの中にあります。
fs.promises を利用する
このようなコールバックの深いネストを避けるために、 Promise が広く使われていますが、Node.js 10ではその Promise を返す fs.promises というAPIが追加されました。
fs.promises APIを使うと上のコードは次のように書けます。
const fs = require('fs').promises;
fs.writeFile('test.txt', 'Hello!')
    .then(() => {
        console.log('test.txtが作成されました');
        return fs.readFile('test.txt', 'utf-8');
    })
    .then((content) => {
        console.log(content);
    })
    .catch((err) => {
        console.log(err.toString());
    });
これでコールバックのネストがなくなりました。
async/await を利用する
async/await を使えばより手続き的に書けるようになります。
const fs = require('fs').promises;
(async () => {
    try {
        await fs.writeFile('test.txt', 'Hello!');
        console.log('test.txtが作成されました');
        const content = await fs.readFile('test.txt', 'utf-8');
        console.log(content);
    } catch(err) {
        console.log(err.toString());
    }
})();