JavaScript asynchronous programming With Promises
or async-await
Async-await
await keyword, added in ECMAScript 2017. These features basically act as syntactic sugar on top of promises, making asynchronous code easier to write and to read afterwards. They make async code look more like old-school synchronous code, so they're well worth learning. This article gives you what you need to know.async keyword, which you put in front of a function declaration to turn it into an async function. An async function is a function that knows how to expect the possibility of the await keyword being used to invoke asynchronous code.function hello() { return "Hello World" };
hello();async function hello() { return "Hello world" };
hello();let Hello= async function() { return "Hiiii...." };
Hello();.then() block:async-await is better than using promises.async-await or promises.Thumb Rules for Using Promises
- Make sure to write both
.catchand.thenmethods for all the promises. - If something needs to be done in both cases use
.finally. - In
Promise.all, the order of the promises are maintained in the values variable, irrespective of which promise was first resolved. - The return type of all the methods in the
Promiseobject, regardless of whether they are static methods or prototype methods, is again aPromise.
Rules for async-await
async and await.
0.async functions return a promise.asyncfunctions use an implicitPromiseto return results. Even if you don’t return a promise explicitly, theasyncfunction makes sure that your code is passed through a promise.awaitblocks the code execution within theasyncfunction, of which it (await statement) is a part.
Let’s start with the async keyword. It can be placed before a function, like this:
async function f() {
return 1;
}The word “async” before a function means one simple thing: a function always returns a promise. Other values are wrapped in a resolved promise automatically.
For instance, this function returns a resolved promise with the result of 1; let’s test it:
async function f() {
return 1;
}
f().then(alert); // 1…We could explicitly return a promise, which would be the same:
async function f() {
return Promise.resolve(1);
}
f().then(alert); // 1So, async ensures that the function returns a promise, and wraps non-promises in it. Simple enough, right? But not only that. There’s another keyword, await, that works only inside async functions, and it’s pretty cool.
Await
The syntax:
// works only inside async functions
let value = await promise;The keyword await makes JavaScript wait until that promise settles and returns its result.
Here’s an example with a promise that resolves in 1 second:
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000)
});
let result = await promise; // wait until the promise resolves (*)
alert(result); // "done!"
}
f();The function execution “pauses” at the line (*) and resumes when the promise settles, with result becoming its result. So the code above shows “done!” in one second.
Let’s emphasize: await literally suspends the function execution until the promise settles, and then resumes it with the promise result. That doesn’t cost any CPU resources, because the JavaScript engine can do other jobs in the meantime: execute other scripts, handle events, etc.
await in regular functionsIf we try to use await in a non-async function, there would be a syntax error:
function f() {
let promise = Promise.resolve(1);
let result = await promise; // Syntax error
}We may get this error if we forget to put async before a function. As stated earlier, await only works inside an async function.
Let’s take the showAvatar() example from the chapter Promises chaining and rewrite it using async/await:
- We’ll need to replace
.thencalls withawait. - Also we should make the function
asyncfor them to work.
async function showAvatar() {
// read our JSON
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
// read github user
let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
let githubUser = await githubResponse.json();
// show the d demo Example
let img = document.createElement('imgs');
img.src = githubUser.avatar_url;
img.className = "promise-example";
document.body.append(img);
// wait 3 seconds
await new Promise((resolve, reject) => setTimeout(resolve, 2000));
img.remove();
return githubUser;
}
showAvatar();await won’t work in the top-level codePeople who are just starting to use await tend to forget the fact that we can’t use await in top-level code. For example, this will not work:
// syntax error in top-level code
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();But we can wrap it into an anonymous async function, like this:
(async () => {
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
...
})();P.S. New feature: starting from V8 engine version 8.9+, top-level await works in modules.
Below you can find the “rethrow” example. Rewrite it using async/await instead of .then/catch.
And get rid of the recursion in favour of a loop in demoGithubUser: with async/await that becomes easy to do.
class HttpError extends Error {
constructor(response) {
super(`${response.status} for ${response.url}`);
this.name = 'HttpError';
this.response = response;
}
}
function loadJson(url) {
return fetch(url)
.then(response => {
if (response.status == 200) {
return response.json();
} else {
throw new HttpError(response);
}
});
}
// Ask for a user name until github returns a valid user
function demoGithubUser() {
let name = prompt("Enter a name?", "iliakan");
return loadJson(`https://api.github.com/users/${name}`)
.then(user => {
alert(`Full name: ${user.name}.`);
return user;
})
.catch(err => {
if (err instanceof HttpError && err.response.status == 404) {
alert("No such user, please reenter.");
return demoGithubUser();
} else {
throw err;
}
});
}
demoGithubUser();We have a “regular” function called f. How can you call the async function wait() and use its result inside of f?
async function wait() {
await new Promise(resolve => setTimeout(resolve, 1000));
return 10;
}
function f() {
// ...what should you write here?
// we need to call async wait() and wait to get 10
// remember, we can't use "await"
}P.S. The task is technically very simple, but the question is quite common for developers new to async/await.
Let's look back at a simple fetch example that we saw in the previous article:
fetch('coffee.jpg')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.blob();
})
.then(myBlob => {
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
})
.catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message);
});By now, you should have a reasonable understanding of promises and how they work, but let's convert this to use async/await to see how much simpler it makes things:
async function myFetch() {
let response = await fetch('coffee.jpg');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
let myBlob = await response.blob();
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}
myFetch()
.catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message);
});It makes code much simpler and easier to understand — no more .then() blocks everywhere!
Since an async keyword turns a function into a promise, you could refactor your code to use a hybrid approach of promises and await, bringing the second half of the function out into a new block to make it more flexible:
async function myFetch() {
let response = await fetch('coffee.jpg');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.blob();
}
myFetch().then((blob) => {
let objectURL = URL.createObjectURL(blob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}).catch(e => console.log(e));You can try typing in the example yourself, or running our live example (see also the source code).
But how does it work?
You'll note that we've wrapped the code inside a function, and we've included the async keyword before the function keyword. This is necessary — you have to create an async function to define a block of code in which you'll run your async code; as we said earlier, await only works inside of async functions.
Inside the myFetch() function definition you can see that the code closely resembles the previous promise version, but there are some differences. Instead of needing to chain a .then() block on to the end of each promise-based method, you just need to add an await keyword before the method call, and then assign the result to a variable. The await keyword causes the JavaScript runtime to pause your code on this line, not allowing further code to execute in the meantime until the async function call has returned its result — very useful if subsequent code relies on that result!
Once that's complete, your code continues to execute starting on the next line. For example:
let response = await fetch('coffee.jpg');The response returned by the fulfilled fetch() promise is assigned to the response variable when that response becomes available, and the parser pauses on this line until that occurs. Once the response is available, the parser moves to the next line, which creates a Blob out of it. This line also invokes an async promise-based method, so we use await here as well. When the result of operation returns, we return it out of the myFetch() function.
This means that when we call the myFetch() function, it returns a promise, so we can chain a .then() onto the end of it inside which we handle displaying the blob onscreen.
You are probably already thinking "this is really cool!", and you are right — fewer .then() blocks to wrap around code, and it mostly just looks like synchronous code, so it is really intuitive.
Adding error handling
And if you want to add error handling, you've got a couple of options.
You can use a synchronous try...catch structure with async/await. This example expands on the first version of the code we showed above:
async function myFetch() {
try {
let response = await fetch('coffee.jpg');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
let myBlob = await response.blob();
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
} catch(e) {
console.log(e);
}
}
myFetch();The catch() {} block is passed an error object, which we've called e; we can now log that to the console, and it will give us a detailed error message showing where in the code the error was thrown.
If you wanted to use the second (refactored) version of the code that we showed above, you would be better off just continuing the hybrid approach and chaining a .catch() block onto the end of the .then() call, like this:
async function myFetch() {
let response = await fetch('coffee.jpg');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.blob();
}
myFetch().then((blob) => {
let objectURL = URL.createObjectURL(blob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
})
.catch((e) =>
console.log(e)
);This is because the .catch() block will catch errors occurring in both the async function call and the promise chain. If you used the try/catch block here, you might still get unhandled errors in the myFetch() function when it's called.
You can find both of these examples on GitHub:

0 Comments