0
import { get } from "request";
import { writeFile } from "fs";

get(
  "https://en.wikipedia.org/wiki/Async/await",
  (requestErr, response, body) => {
    if (requestErr) {
      console.error(requestErr);
    } else {
      writeFile("async.js", body, writeErr => {
        if (writeErr) {
          console.error(writeErr);
        } else {
          console.log("File written");
        }
      });
    }
  }
);

From what i understand, the writeFile function needs to be launched asynchronously because it needs to wait for the get function to be finished first (fetching the URL). However, I'm really not sure how to refactor it as such.

lontonglah
  • 23
  • 6

2 Answers2

1
import { get } from "request";
import { writeFile } from "fs/promises";
import { promisify } from "util";

const getAsync = promisify(get);

async function main() {
  let body;
  try {
    ({ body } = await getAsync("https://en.wikipedia.org/wiki/Async/await"));
  } catch(requestErr) {
    console.error(requestErr);
    return;
  }
  
  try {
    await writeFile("async.js", body);

    console.log("File written");
  } catch (writeErr) {
    console.error(writeErr);
  }
}

main();

Generic guide to to transform any callback flow into promises and async/await

Check if the library has a promise API

If yes, use it. A lot of libraries have a dual interface where you can call them with a callback or you can call them without and they return you a promise. Or they might have an alternative import to get the promise version.

If not, you would need to convert the callbacks to promises. Keep in mind that this can be done via an existing library.

One final alternative is to use a different library but it's a bit extreme. You'll have to check if it works the same way to how you're using the current one. Still, it's sometimes a good option, if there is a newer and better maintained library.

Transform the callbacks to async/await

In general the transformation looks like this:

From

callbackAPI(data, function(error, result) {
    if (error) {
        //handle error
    } else {
        //process result
    }
});

To

//assume the promisifiedAPI is the promise returning alternative of callbackAPI

try {
    const result = await promisifiedAPI(data);
    //process result
} catch (error) {
    //handle error
}

This might need some alterations depending on the exact usage but usually a callback has an error and result as two parameters. This is transformed into a try..catch construct where the now promisified call is awaited and returns then handles result in the absence of a problem is handled in the try. Error handling goes in the catch block.

Also if there is no top level await yet, you need to wrap the entire async code into an async function() {} or async () => {} otherwise you cannot use await.


Steps taken here

Check if the library has a promise API

request

The library doesn't have a built in promise API. The documentation suggests to use libraries that convert the calls to promises. An easy way is to use utils.promisify() to achieve that, since it's already in Node.JS.

While checking for that, I also came upon the How do you properly promisify request? (answer by Little Roys) which is exactly about this library. The code there is (changed it to only focus on get):

import { get, post } from "request";
import { promisify } from "util";

const [getAsync, postAsync] = [get, post].map(promisify);

or if we just want to focus on get()

import { get } from "request";
import { promisify } from "util";

const getAsync = promisify(get);

Also worth noting that the request package has been deprecated. In this case it's a viable alternative to change to a different library. Not needed but a possibility. I'll use this one throughout the example to showcase how normal conversion happens.

fs

It does have a promise API and you can just import things from "fs/promises". Here is the promise version of writeFile.

In this case it's enough to change

import { writeFile } from "fs";

to

import { writeFile } from "fs/promises";

Transform the callbacks to async/await

require

From
get(
  "https://en.wikipedia.org/wiki/Async/await",
  (requestErr, response, body) => {
    if (requestErr) {
      console.error(requestErr);
    } else {
      //omitted for brevitiy
    }
  }
To
let body;
try {
  ({ body } = await getAsync("https://en.wikipedia.org/wiki/Async/await"));
} catch(requestErr) {
  console.error(requestErr);
  return;
}

This will call the now promisified getAsync and extract the body from the response.

If there is an error, we handle it in the catch block. The return; is there to immediately terminate the function if there is an error. I'll come back to this later.

Now that we have the body of the response, we can continue

fs

From
writeFile("async.js", body, writeErr => {
  if (writeErr) {
    console.error(writeErr);
  } else {
    console.log("File written");
  }
});
To
try {
  await writeFile("async.js", body);

  console.log("File written");
} catch (writeErr) {
  console.error(writeErr);
}

We make a call to writeFile using await which ensures the execution will resume only after the operation completes. We don't get the response from it, we just monitor for errors with try..catch and handle them if any arise. If the writeFile succeeds we just go to the console.log("File written");

async function main()

async function main() {
  //execute promisified operations
}

main();

The whole block is wrapped into an async function, so await can be used. We then call that function. It's a bit more readable that way. As mentioned, you might have support for top level await. That might either be in the future or via transpilation. Or maybe your code is already in an asynchronous function. In those cases you wouldn't need another asynchronous function but I wanted to showcase this just in case.

try {} catch(error) { return; }

Finally, I want to address this construct. I don't like the nesting of blocks. You can avoid it if the code is re-written this way (code snippet collapsed for brevity):

import { get } from "request";
import { writeFile } from "fs/promises";
import { promisify } from "util";

const getAsync = promisify(get);

async function main() {
  
  try {
    const { body } = await getAsync("https://en.wikipedia.org/wiki/Async/await");
    
    try {
      await writeFile("async.js", body);

      console.log("File written");
    } catch (writeErr) {
      console.error(writeErr);
    }
  } catch(requestErr) {
    console.error(requestErr);
  }
}

main();

However, it starts to become a problem if you need multiple async calls that will further nest the blocks. I prefer an early exit via return which keeps the blocks at one level. An alternative is to use a throw statement, if you prefer (and are able to) handle the failure further up the call chain. Both achieve the same thing nesting-wise.

VLAZ
  • 26,331
  • 9
  • 49
  • 67
0

Looking for something like this?

var axios = require("axios");
var fs = require("fs");

const fetchAndWrite = async () => {
  try {
    const { data } = await axios.get(
      "https://en.wikipedia.org/wiki/Async/await"
    );
    await fs.promises.writeFile("/some_directory/async-await.html", data);
  } catch (error) {
    // something bad happened...
  }
};

fetchAndWrite();
Oleg
  • 654
  • 1
  • 7
  • 16