Using Async in ES2017 - DiscoverSDK Blog

June 07, 2018 0 Comments

Using Async in ES2017 - DiscoverSDK Blog

 

 

I know that async sounds like an awful boy band from the 90’s, but the truth is that it’s one of the most anticipated new features of ES2017. As a reminder, one can already use it with Babel. It’s quite new, so much so that it’s not even in Node 8—and we won’t mention browser support.

It’s a feature that can be a little hard to understand, so don’t just skip down to the code examples—read the text of the article from start to finish. I promise it’ll be worth it, because this is a pretty ingenious feature. Ready? Here it goes:

We all know promises, right? If you’re not familiar with them, this is a good time to read our article on them from ES2015. We’re talking about a very easy way to work with code that takes time to be executed afterward—for instance reading from an API, database, or files. Here—I’ll demonstrate through code that simply waits a few seconds until it returns:

function someService() { return new Promise(function(resolve, reject) { setTimeout(ev => { resolve('Promise result123'); }, 500); }); 
} console.log('start of code');
someService().then(result => { console.log(result);
}); console.log('end of code');
//Will print: //"start of code" //"end of code" //"Promise result123"

What’s going on here? Something an experienced JS programmer should be familiar with. someService is a function which in principle returns a promise after half a second.  This is just an example but it could be something real, like a call to an API or to a file. What’s important is that I use it via then. This is the standard use of a promise and I’ll show that the moment the promise is executed, whatever it returns is printed.

If this code seems unclear to you because of all of the => and the let, it’s a good time to start working with ES6, because that’s the syntax we’ll be using.

With async I can do the same thing, but with a lot less code. Take a look at this code:

function someService() { return new Promise(function(resolve, reject) { setTimeout(ev => { resolve('Promise result123'); }, 500); });  
}
async function main() { let result = await someService(); console.log('result' + result);  
}
console.log('start of code');  
main();  
console.log('end of code');  
//Will print:
//"start of code"
//"end of code"
//"Promise result123"

What’s interesting and important here is the main. Instead of using then, we use await. Without resolve and without a big mess! What we do is define some function as asynchronous, which we do by using the keyword async before function. From that moment we can use await without issue. The await really happens as soon as the promise is executed. That is to say, as soon as we have resolve without needing to use them!

Here’s a live example. Notice that I ran Babel with the async plugin and the polyfil:

If we’re looking for a slightly more tangible example, we can look at the following:

function jokeService() { return new Promise(function(resolve, reject) { var request = new XMLHttpRequest(); request.open('GET', 'https://api.icndb.com/jokes/random'); request.onload = function() { if (request.status == 200) { resolve(JSON.parse(request.response)); // we got data here, so resolve the Promise } else { reject(Error(request.statusText)); // status is not 200 OK, so reject } }; request.onerror = function() { reject(Error('Error fetching data.')); // error occurred, reject the Promise }; request.send(); //send the request });  
}
async function main() { let result = await jokeService(); document.write(result.value.joke);  
}
main();

We could start to analyze jokeService but it’s not very interesting. Long story short, it’s an asynchronous call to some API—what’s important is that it returns a promise. I can call it with then, and then extract the result. But why? I can do the same thing in a much more intuitive and easier way. I’ll create a function and use the result of jokeService as soon as it’s ready in regular code using async/wait. It’s so easy! await simply replaces then. That’s it! That’s the big deal!

Here’s a live example that you can mess around with:

Async is still easy to use when you have multiple promises.

function jokeService() { return new Promise(function(resolve, reject) { var request = new XMLHttpRequest(); request.open('GET', 'https://api.icndb.com/jokes/random'); request.onload = function() { if (request.status == 200) { resolve(JSON.parse(request.response)); // we got data here, so resolve the Promise } else { reject(Error(request.statusText)); // status is not 200 OK, so reject } }; request.onerror = function() { reject(Error('Error fetching data.')); // error occurred, reject the Promise }; request.send(); //send the request });  
}
function fortuneService() { return new Promise(function(resolve, reject) { var request = new XMLHttpRequest(); request.open('GET', 'https://helloacm.com/api/fortune/'); request.onload = function() { if (request.status == 200) { resolve(request.response); // we got data here, so resolve the Promise } else { reject(Error(request.statusText)); // status is not 200 OK, so reject } }; request.onerror = function() { reject(Error('Error fetching data.')); // error occurred, reject the Promise }; request.send(); //send the request });  
}
async function main() { let joke = await jokeService(); let fortune = await fortuneService(); document.write(joke.value.joke + fortune);  
}
main();

Here we have two services: jokeService and fortuneService. They’re both asynchronous since they go out to the server and return results from an API. Instead of going crazy with all and then, I just use async and await. Just look how nice and easy, and most importantly, intuitive. Only once both services are ready, they return the results and the code inside the asynchronous function can run.

Here’s a live example:

Those with more experience with promise should see the problem here right away. The problem is that, as opposed to then, we don’t have a way to decide what happens if the promise fails. In normal use of promise, the then receives two functions—one that decides what happens if the promise is executed and the other if it is not. Here’s an example:

function someService() { return new Promise(function(resolve, reject) { setTimeout(ev => { reject('Promise rejected'); }, 500); });  
}
someService().then( result => {}, //Will never happened err => {console.log(err)} //Promise rejected  
)

And a live example:

So what do we have here? I have someService that always gets to reject. When I call it, I pass two functions in then. The first one is called if everything went OK, and the second in the case of reject. Since someService is a bad boy, it will always get to reject, and we’ll always get an error printed. So how do we do something like this with async and await? It’s easy—with try and catch! Since async goes back to standard JavaScript syntax, there’s no problem to use the regular syntax of the language!

function someService() { return new Promise(function(resolve, reject) { setTimeout(ev => { reject('Promise rejected :P'); }, 500); });  
}
async function main() { let result; try { result = await someService(); console.log(result); //Will never happen } catch(err) { document.write(err); //"Promise rejected :P" }  
}
main();

Really quite easy, no? Here’s the live example if you want to mess around:

As you can see, despite the fact that async looks a little scary, it actually makes life a lot easier. There’s no reason not to use it already today and convert your usage of promises to more elegant code. Don’t forget that promises are not disappearing, and we use them to create the asynchronous functions we call.

Though, at the moment, there is not a good enough way to emulate promise.all.

Async is all in all syntactic sugar above the generators. That is to say, the mechanism that activates it is that of the generators, and works exactly the same. Await is more or less yield. If you look at Babel’s code, you’ll see it does a transformation from async to generators. But it’s not necessary for initial use and understanding of async.





Previous article: ES2016 New Features

Next article: coming soon!

About the author: Ran Bar-Zik is an experienced web developer whose personal blog, Internet Israel, features articles and guides on Node.js, MongoDB, Git, SASS, jQuery, HTML 5, MySQL, and more. Translation of the original article by Aaron Raizen.

By Ran Bar-Zik | 5/29/2018 | General


Tag cloud