user1157885
user1157885

Reputation: 2069

Async functions vs await

I'm coming from a PHP background and I'm trying to learn NodeJS.

I know that everything in Node is async but I've found that i've been using the async / await combo quite a lot in my code and I wanted to make sure I wasn't doing something stupid.

The reason for it is that I have a lot of situations where I need the result of something before continuing (for example for a small ajax request). This small ajax request has no other purpose other than to do the set of things that I want it to and in the order that I specify so even if I do things the "async way" and write it using a callback i'm still having to wait for things to finish in the right order.

Right now whenever I find myself in this situation I just use await to wait for the result:

ie:

var result = await this.doSomething(data);

Opposed to using a callback

this.doSomething(data, function(callback) {
  // code
  callback();
});

To me the first example looks cleaner than the second one which is why I've been opting for that. But I'm worried that I might be missing something fundamental here. But in a situation where there is nothing else to process below the async call and the only way for things to progress is for it to follow a syncronous style, is there anything wrong with using the first style over the second?

Upvotes: 3

Views: 1189

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074008

But I'm worried that I might be missing something fundamental here.

Nope, you're not, that's exactly what you want to do, assuming this.doSomething(data) is asynchronous (and if it's an ajax call, one hopes it is async) and that it returns a promise (which all functions defined with the async keyword do implicitly). (If it's not asynchronous, you don't need the await, although it's allowed.)

You're probably being a bit confused (understandably) by the fact that things are going through a transition. Until recently, the overwhelming convention in Node APIs (the built-in ones and ones provided by third-party modules) was to use the "Node callback" pattern, which is that a function that will do asynchronous work expects its last argument to be a callback, which it will call with a first argument indicating success/failure (null = success, anything else is an error object) with subsequent arguments providing the result. (Your second example assumes doSomething is one of these, instead of being a function that returns a promise.)

Example: fs.readFile, which you use like this:

fs.readFile("/some/file", "utf-8", function(err, data) {
    if (err) {
        // ...handle the fact an error occurred..
        return;
    }
    // ...use the data...
});

This style quickly leads to callback hell, though, which is one of the reasons promises (aka "futures") were invented.

But a lot of Node APIs still use the old pattern.

If you need to use "Node callback"-pattern functions, you can use util.promisify to create promise-enabled versions of them. For instance, say you need to use fs.readFile, which uses the Node callback pattern. You can get a promise version like this:

const readFilePromise = util.promisify(fs.readFile);

...and then use it with async/await syntax (or use the promise directly via then):

const data = await readFilePromise("/some/file", "utf-8");

There's also an npm module called promisify that can provide a promise-ified version of an entire API. (There's probably more than one.)

Just because promises and async/await replace the old Node callback style in most cases doesn't mean callbacks don't still have a place: A Promise can only be settled once. They're for one-off things. So callbacks still have a place, such as with the on method of EventEmitters, like a readable stream:

fs.createReadStream("/some/file", "utf-8")
    .on("data", chunk => {
        // ...do something with the chunk of data...
    })
    .on("end", () => {
        // ...do something with the fact the end of the stream was reached...
    });

Since data will fire multiple times, it makes sense to use a callback for it; a promise wouldn't apply.

Also note that you can only use await in an async function. Consequently, you may find yourself getting into the habit of a "main" module structure that looks something like this:

// ...Setup (`require` calls, `import` once it's supported, etc.)...
(async () => {
    // Code that can use `await `here...
})().catch(err => {
    // Handle the fact an error/promise rejection occurred in the top level of your code
});

You can leave the catch off if you want your script to terminate on an unhandled error/rejection. (Node doesn't do that yet, but it will once unhandled rejection detection matures.)

Alternately, if you want to use a promise-enabled function in a non-async function, just use then and catch:

this.doSomething()
    .then(result => {
        // Use result
    })
    .catch(err => {
        // Handle error
    });

Note: async/await is directly supported in Node 7.x and above. Be sure your target production environment supports Node 7.x or above if your'e going to use async/await. If not, you could transpile your code using something like Babel and then use the transpiled result on the older version of Node.

Upvotes: 6

Related Questions