The debut of Promises in JavaScript has lit the internet on fire—they help developers break out of callback hell and solve a lot of problems that have plagued the asynchronous code of JavaScript programmers everywhere. Promises are far from flawless, though. They still require callbacks, can still be messy in complex situations, and are incredibly verbose.
22nd March 2017: This article has been updated to reflect changes to the specification, and current runtime support.
With the advent of ES6 (referred to as ES2015 from here on), which not only made promises native to the language without requiring one of the countless available libraries, we also got generators. Generators have the ability to pause execution within a function, which means that by wrapping them in a utility function, we have the ability to wait for an asynchronous operation to finish before moving on to the next line of code. Suddenly your asynchronous code could start to look synchronous!
But that was just the first step. Async functions are due to be released as part of ES2017 this year and native support is already growing. Async functions take the idea of using generators for asynchronous programming and give them their own simple and semantic syntax. Consequently, you don’t have to use a library to get that wrapping utility function, because that is handled in the background.
To run the async/await code samples from this article, you'll need a compatible browser.
Runtime Compatibility
On the client-side, Chrome, Firefox and Opera now support async functions out of the box.
As of version 7.6, Node.js also ships with async/await enabled by default.
Async Functions vs Generators
Here is an example of using generators for asynchronous programming. It uses the Q library:
var doAsyncOp = Q.async(function* () {
var val = yield asynchronousOperation();
console.log(val);
return val;
});
Q.async
is the wrapper function that handles everything behind the scenes. The *
is what denotes the function as a generator function and yield
is how you pause the function and let the wrapper function take over. Q.async
will return a function that you can assign—as I have done—to doAsyncOp
and subsequently invoke.
Here’s what it looks like when you get rid of the cruft by using the new syntax included in ES7:
async function doAsyncOp () {
var val = await asynchronousOperation();
console.log(val);
return val;
};
It’s not a lot different, but we removed the wrapper function and the asterisk and replaced them with the async
keyword. The yield
keyword was also replaced by await
. These two examples will do the exactly same thing: wait for asynchronousOperation
to complete before assigning its value to val
, logging it, and returning it.
Converting Promises to Async Functions
What would the previous example look like if we were using vanilla promises?
function doAsyncOp () {
return asynchronousOperation().then(function(val) {
console.log(val);
return val;
});
};
This has the same number of lines, but there is plenty of extra code due to then
and the callback function passed to it. The other nuisance is the duplication of the return
keyword. This has always been something that bugged me because it makes it difficult to figure out exactly what is being returned from a function that uses promises.
As you can see, this function returns a promise that will fulfill to the value of val
. And guess what … so do the generator and async function examples! Whenever you return a value from one of those functions, you are actually implicitly returning a promise that resolves to that value. If you don’t return anything at all, you are implicitly returning a promise that resolves to undefined
.
Chaining Operations
One of the aspects of promises that hooks many people is the ability to chain multiple asynchronous operations without running into nested callbacks. This is one of the areas in which async functions excel even more than promises.
This is how you would chain asynchronous operations using promises (admittedly we’re being silly and just running the same asynchronousOperation
over and over again).
function doAsyncOp() {
return asynchronousOperation()
.then(function(val) {
return asynchronousOperation(val);
})
.then(function(val) {
return asynchronousOperation(val);
})
.then(function(val) {
return asynchronousOperation(val);
});
}
With async functions, we can just act like asynchronousOperation
is synchronous:
async function doAsyncOp () {
var val = await asynchronousOperation();
val = await asynchronousOperation(val);
val = await asynchronousOperation(val);
return await asynchronousOperation(val);
};
You don’t even need the await
keyword on that return statement because either way, it will return a promise resolving to the final value.
Parallel Operations
One of the other great features of promises is the ability to run multiple asynchronous operations at once and continue on your way once all of them have completed. Promise.all()
is the way to do this according to the ES2015 spec.
Here’s an example:
function doAsyncOp() {
return Promise.all([
asynchronousOperation(),
asynchronousOperation()
]).then(function(vals) {
vals.forEach(console.log);
return vals;
});
}
Continue reading %Simplifying Asynchronous Coding with Async Functions%
by Joe Zimmerman via SitePoint
No comments:
Post a Comment