Markus Schmidlich
Markus Schmidlich

Reputation: 1451

fs.writeFile in a promise, asynchronous-synchronous stuff

I need some help with my code. I'm new at Node.js and have a lot of trouble with it.

What I'm trying to do:

  1. Fetch a .txt with Amazon products (ASINs) ;

  2. Fetch all products using the amazon-product-api package;

  3. Save each product in a .json file.

My code is not working. I think I messed up with this asynchronous-synchronous stuff - help me!

var amazon = require('amazon-product-api');
var fs = require('fs');

var client = amazon.createClient({
    awsId: "XXX",
    awsSecret: "XXX",
    awsTag: "888"
});

var array = fs.readFileSync('./test.txt').toString().split('\n');
for (var i = 1; i < array.length; i++) {
     var ASIN = array[i];

    client.itemLookup({
            domain: 'webservices.amazon.de',
            responseGroup: 'Large',
            idType: 'ASIN',
            itemId: ASIN
        })
        .then(function(results) {
            fs.writeFile(ASIN + '.json', JSON.stringify(results), function(err) {
                if (err) {
                    console.log(err);
                } else {
                    console.log("JSON saved");
                }
            })

            return results;

        }).catch(function(err) {
            console.log(err);
        });
};

Upvotes: 135

Views: 205845

Answers (12)

wraiford
wraiford

Reputation: 671

2023, Node v16+, ESM, import, using promises

(I've also refactored the code a bit to avoid nested try..catch blocks, YMMV.

import * as fs from 'node:fs/promises';

async function doFile(filepath) { // choose a better name than `doFile` for your use case!!
    try {
        const fileData = await fs.readFile(filepath, {encoding: 'utf8'});
        var array = fileData.toString().split('\n');
        for (var i = 1; i < array.length; i++) {
            var ASIN = array[i];
            let results = await doClientStuff(args); // e.g. client.itemLookup
            await saveResults(ASIN, results);
        };
    } catch (error) {
        console.error(error?.message ?? error?.toString()); // or however you extract your error msg
    }
}
async function saveResults(ASIN, results) {
    try {
        await fs.writeFile(ASIN + '.json', JSON.stringify(results));
        console.log("JSON saved");
    } catch (error) {
        console.error(error?.message ?? error?.toString()); // or however you extract your error msg
    }
}
await doFile('./test.txt');

This uses the OP's desire to us a fs default import. I personally would change it to import only your individual functions and remove the fs. references to enable esm tree-shaking:

import { readFile, writeFile } from 'node:fs/promises';

const fileData = await readFile(filepath, {encoding: 'utf8'}); // fs. removed

await writeFile(ASIN + '.json', JSON.stringify(results)); // fs. removed

further info

If no encoding is specified (using options.encoding), the data is returned as a object. Otherwise, the data will be a string.

Refer to Node documentation for additional info, including alternative usage with sync/callbacks. Be sure to select your correct version in the docs for your Node environment.

Upvotes: 6

luispa
luispa

Reputation: 323

Use require('fs/promises')

var fs = require('fs/promises'); // Since 11.14.0
var path = require('path'); // to help us to join better the paths

var content = JSON.stringify(["this is your content"]); // Must be a string to be written.

fs
  .writeFile(path.join(__dirname, 'test.json'), content, { encoding: 'utf8' })
  .then(() => {
    console.log('Write is done!');
  });

Example using async/await

var fs = require('fs/promises'); // Since 11.14.0
var path = require('path'); // to help us to join better the paths

var content = JSON.stringify(["this is your content"]); // Must be a string to be written.

(async function autorun(){
  
  await fs.writeFile(path.join(__dirname, 'test.json'), content, { encoding: 'utf8' })  
  console.log('Write is done!');

})() // This is called a IIFE: Immediately invoked function expression

Upvotes: 3

Rahul Sadaphal
Rahul Sadaphal

Reputation: 1

Use fs.writeFileSync inside the try/catch block as below.

var fs = require('fs');
try {
    const file = fs.writeFileSync(ASIN + '.json', JSON.stringify(results))
    console.log("JSON saved");
    return results;
} catch (error) {
    console.log(err);
}

Upvotes: -3

Divine Hycenth
Divine Hycenth

Reputation: 670

What worked for me was fs.promises.

Example One:

const fs = require("fs")

fs.promises
  .writeFile(__dirname + '/test.json', "data", { encoding: 'utf8' })
  .then(() => {
    // Do whatever you want to do.
    console.log('Done');
  });

Example Two. Using Async-Await:

const fs = require("fs")

async function writeToFile() {
  await fs.promises.writeFile(__dirname + '/test-22.json', "data", {
    encoding: 'utf8'
  });

  console.log("done")
}

writeToFile()

Upvotes: 15

Kim Kern
Kim Kern

Reputation: 60527

If you want to import the promise based version of fs as an ES module you can do:

import { promises as fs } from 'fs'

await fs.writeFile(...)

As soon as node v14 is released (see this PR), you can also use

import { writeFile } from 'fs/promises'

Upvotes: 18

AntonB
AntonB

Reputation: 2863

Because fs.writefile is a traditional asynchronous callback - you need to follow the promise spec and return a new promise wrapping it with a resolve and rejection handler like so:

return new Promise(function(resolve, reject) {
    fs.writeFile("<filename.type>", data, '<file-encoding>', function(err) {
        if (err) reject(err);
        else resolve(data);
    });
});

So in your code you would use it like so right after your call to .then():

 .then(function(results) {
    return new Promise(function(resolve, reject) {
            fs.writeFile(ASIN + '.json', JSON.stringify(results), function(err) {
               if (err) reject(err);
               else resolve(data);
            });
    });
  }).then(function(results) {
       console.log("results here: " + results)
  }).catch(function(err) {
       console.log("error here: " + err);
  });

Upvotes: 65

user6269864
user6269864

Reputation:

As of 2019...

...the correct answer is to use async/await with the native fs promises module included in node. Upgrade to Node.js 10 or 11 (already supported by major cloud providers) and do this:

const fs = require('fs').promises;

// This must run inside a function marked `async`:
const file = await fs.readFile('filename.txt', 'utf8');
await fs.writeFile('filename.txt', 'test');

Do not use third-party packages and do not write your own wrappers, that's not necessary anymore.

No longer experimental

Before Node 11.14.0, you would still get a warning that this feature is experimental, but it works just fine and it's the way to go in the future. Since 11.14.0, the feature is no longer experimental and is production-ready.

What if I prefer import instead of require?

It works, too - but only in Node.js versions where this feature is not marked as experimental.

import { promises as fs } from 'fs';

(async () => {
    await fs.writeFile('./test.txt', 'test', 'utf8');
})();

Upvotes: 288

Lewis
Lewis

Reputation: 14926

Finally, the latest node.js release v10.3.0 has natively supported fs promises.

const fsPromises = require('fs').promises; // or require('fs/promises') in v10.0.0
fsPromises.writeFile(ASIN + '.json', JSON.stringify(results))
  .then(() => {
    console.log('JSON saved');
  })
  .catch(er => {
    console.log(er);
  });

You can check the official documentation for more details. https://nodejs.org/api/fs.html#fs_fs_promises_api

Upvotes: 25

Saeed Zarinfam
Saeed Zarinfam

Reputation: 10200

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

const fs_writeFile = util.promisify(fs.writeFile)

fs_writeFile('message.txt', 'Hello Node.js')
    .catch((error) => {
        console.log(error)
    });

Upvotes: 2

amara
amara

Reputation: 2286

say

const util = require('util')
const fs_writeFile = util.promisify(fs.writeFile)

https://nodejs.org/api/util.html#util_util_promisify_original

this is less prone to bugs than the top-voted answer

Upvotes: 63

rouan
rouan

Reputation: 7289

Update Sept 2017: fs-promise has been deprecated in favour of fs-extra.


I haven't used it, but you could look into fs-promise. It's a node module that:

Proxies all async fs methods exposing them as Promises/A+ compatible promises (when, Q, etc). Passes all sync methods through as values.

Upvotes: 4

trquoccuong
trquoccuong

Reputation: 2873

For easy to use asynchronous convert all callback to promise use some library like "bluebird" .

      .then(function(results) {
                fs.writeFile(ASIN + '.json', JSON.stringify(results), function(err) {
                    if (err) {
                        console.log(err);
                    } else {
                        console.log("JSON saved");
                        return results;
                    }
                })


            }).catch(function(err) {
                console.log(err);
            });

Try solution with promise (bluebird)

var amazon = require('amazon-product-api');
var fs = require('fs');
var Promise = require('bluebird');

var client = amazon.createClient({
    awsId: "XXX",
    awsSecret: "XXX",
    awsTag: "888"
});


var array = fs.readFileSync('./test.txt').toString().split('\n');
Promise.map(array, function (ASIN) {
    client.itemLookup({
        domain: 'webservices.amazon.de',
        responseGroup: 'Large',
        idType: 'ASIN',
        itemId: ASIN
    }).then(function(results) {
        fs.writeFile(ASIN + '.json', JSON.stringify(results), function(err) {
            if (err) {
                console.log(err);
            } else {
                console.log("JSON saved");
                return results;
            }
        })
    }).catch(function(err) {
        console.log(err);
    });
});

Upvotes: -2

Related Questions