0

I'm writing a script which will open all of the bookmarks I put in a particular folder. I'm using "fs" module's "readFile" method to get the data from the bookmarks file, and some utility methods to drill the data down and construct an array of urls to open.

The trouble I am having is that though the file data is accessable, and I can manipulate it with the other methods in my class, I do not seem to be able to return the result from the "fs.readFile" callback. Here is the code (I can add the rest of the class as well if someone would find that helpfull)

readBookmarks() {
    fs.readFile(path.join(homeDir() + "/Library/Application " + "Support/Google/Chrome/Default/Bookmarks"),
        (error, data) => {
        if (error) {
            throw error;
        }
        if (data){
            console.log(this.constructUrlList(data)); //outputs an array of urls like I expect
            return this.constructUrlList(data); // returns undefined
        }
    });
}

I feel like I'm missing something. I get that "fs.readFile" is async, but should the callback only fire once the file buffer is loaded? And if not why is the data available to "console.log"?

Thanks for help

UPDATE: Thanks to reading the dup jfriend00 posted I now understand a good pattern for modular, asynchronous js. Here is the refactored code:

openLinks() {
    this.readBookmarks().then((data) => {
        const urlList = this.constructUrlList(data);
        for(let url of urlList) {
            opn(url, {app: 'Safari'});
        }
        process.exit();
    }).catch((error) => {
        console.error("Unable to read the file due to error: " + error.stack);
    })
}

readBookmarks() {
    return new Promise((resolve, reject) => {
        fs.readFile(path.join(homeDir(), "/Library/Application Support/Google/Chrome/Default/Bookmarks"),
            (error, data) => {
            error ? reject(error) : resolve(data);
        });
    });
}
HelloWorld
  • 2,480
  • 3
  • 28
  • 45
  • 1
    `readFile` is Asynchronously invoked, – Satpal May 16 '17 at 04:59
  • It is async, but shouldn't the callback only be called once the operation resolves with an error or the file buffer is loaded? And if not, then how does console.log get the data? – HelloWorld May 16 '17 at 05:00
  • The data is returned from the anonymous function `(error, data) => {..}` not from the `fs.readFile` function. – Titus May 16 '17 at 05:06
  • Use the `path.join` from the `path` module to concatenate paths. Don't treat paths as strings. – arboreal84 May 16 '17 at 05:07
  • I will suggest you to use promises and return promise from readBookMarks method and get resolved value from there. – WitVault May 16 '17 at 05:08
  • @arboreal84 - I had a quick read and see that path module can solve cross OS path syntax issues before they start. Thanks - I've updated my code – HelloWorld May 16 '17 at 05:12
  • Needs to be: `path.join(['a', 'b', 'c'])` rather than `path.join('a' + 'b' + 'c')` though. – arboreal84 May 16 '17 at 05:13
  • Your `return this.constructUrlList(data)` returns back into `fs.readFile()`. It doesn't return from your `readBookmarks()` function. That function has already returned nothing, long before the `fs.readFile()` callback even gets called. Asynchronous callbacks are called at some indeterminate time in the future AFTER your `readBookmarks()` function has already returned - so you can't return the data directly from `readBookmarks()`. Instead, see the question yours has been marked a dup of for ways to get the data out (callbacks or promises). – jfriend00 May 16 '17 at 05:13
  • In a nutshell, you can't synchronously return an asynchronously retrieved value. An async value can only be returned asynchronously (via a promise or callback function). – jfriend00 May 16 '17 at 05:15
  • @jfriend - so basically, I need to call a promise inside of readBookmarks, which will force readBookmarks to wait until fs.readFile returns something, and then process the result or throw an error? – HelloWorld May 16 '17 at 05:17
  • No. You need to return a promise from `readBookmarks()` that you resolve when the `fs.readFile()` operation is done and the caller then uses `.then()` on the promise to be notified when the async operation is done. You cannot make Javascript "wait" for an async operation to be done before returning. It doesn't do that. The details are in that duplicate if you study it. – jfriend00 May 16 '17 at 05:25
  • OK I will have a look at it in depth – HelloWorld May 16 '17 at 05:27
  • Hey @jfriend00: FYI I read the in depth dup you linked to and now finally understand a good pattern for modular, asynchronous js. readBookmarks() { return new Promise((resolve, reject) => { fs.readFile(path.join(homeDir(), "/Library/Application Support/Google/Chrome/Default/Bookmarks"), (error, data) => { error ? reject(error) : resolve(data); }); }); } – HelloWorld May 16 '17 at 16:21
  • @HelloWorld - Yep, that's the idea. – jfriend00 May 16 '17 at 17:43

0 Answers0