Introduction

Using only one promise is clear and simple, but when the program is complicated by asynchronous logic, working with promises quickly becomes difficult. With the ES2017 standard, a new syntax “async/await” has arrived, which makes it easier to work with promises, and enables a simpler presentation of a series of asynchronous promises. Using the “async/await” syntax allows us to display multiple interdependent asynchronous actions more readably and thus avoid the so-called “promise hell”. It should be noted that “ordinary” callback functions cannot be used with async functions.
“Async/await” syntax does not exclude promises, but changes the way of “consumption” of promises. Async/Await syntax allows us to write asynchronous code according to the sequential order of execution so that it looks like synchronous code.
Asynchronous Function Syntax
Marking the function with “async”
An asynchronous function is a function within which asynchronous code is executed. An asynchronous function is marked with the “async” keyword. A function marked like this lets the compiler know that an asynchronous operation will be performed inside it.
|
1 |
async function nekaAsinhornaFunkcija(){...} |
The tasks that the async function performs in the background are:
- Automatically converts a regular function to a promise
- Anything returned with “return” in the function body becomes “Promise.resolve” after a successful operation
1234function getNumber() { = async function getNumber() {return Promise.resolve(4) = return 4 // ReturnsPromise.resolve(4)} = }getNumber().then(res => console.log(res)); = getNumber().then(res => console.log(res)); // Returns: 4
An Async function always returns a “promise”, even if the returned value is not a “promise”. The async function “wraps” each returned value and passes it as a Promise. - Async function allows using await operator.
“Await” operator
This operator can only be used within the “async” function, where it pauses the execution of the async function. The reserved word “await” pauses the execution of the async function until it receives the results of the operation ie. until the promise is returned. If the Promise is in the state fulfilled, then the result that “awaited” the await has the “fulfillment” value, and if the promised Promise is in the “rejected” state, then the await forwards the “rejection” value.
Error handling
The standard try/catch syntax is used with the await operator. The await operator “waits” for a promise, and if the operation is successful, the async function returns “promise resolved”. In case of an unsuccessful operation, it passes “promise rejected”, while the “catch” block is used to “catch” that “exception”.
|
1 2 3 4 5 6 7 8 9 |
function displayUserData() { return fetch('/users/pera') .then(function(pera) { console.log(pera); }) .catch(function (err) { console.error('Uhh imamo problem!', err); }) }) |
|
1 2 3 4 5 6 7 8 9 |
async function displayUserData() { try { let pera = await fetch('/users/pera'); console.log(pera); } catch(err) { console.error('Uhh imamo problem!', err); } } |
Explanation:
Using async/await syntax allows javascript to better manage errors (especially stack trace), and to use memory in a more efficient way (eng. memory-efficient).
Parallelism
Async operations are executed in series one after the other, so they should not be confused with “Promise.all” where promises are executed in parallel. For parallel execution of promises, it is best to use “Promise.all” syntax. It should be emphasized that the “async/await” syntax does not exclude the “promise.all” syntax, even more together they give their maximum and the code is clear and reviewed. The following examples will show the difference between serial and parallel execution.
Serial execution
This is an example of batch execution because another “await” is waiting for the first operation to be executed.
|
1 2 3 4 |
async function series() { const result1 = await wait(500); const result2 = await wait(500); } // Ukupno traje 1000ms! |
Parallel execution
If we want the operations to be executed in parallel, we need to let both functions be executed in parallel, and only then “wait” for them with the await operator:
|
1 2 3 4 5 6 7 |
async function parallel() { const result1 = wait(500); const result2 = wait(500); await result1; await result2; return "done!"; } // It takes only 500ms in total! |
Symbiosis of Promise.all and async/await syntax
|
1 2 3 4 5 6 |
async function foo() { const results = await Promise.all([ wait(500), wait(500) ]); } |
With destructuring we can assign the returned values individually:
|
1 2 3 4 5 6 |
async function foo() { const [result1, result2] = await Promise.all([ wait(500), wait(500) ]); } |
Iteration of asynchronous operation
for…of
It is recommended to use a “for…of” loop for iteration in an asynchronous function:
|
1 2 3 4 5 6 |
async function obradaNizaPromisa (){ for (const item of niz) { await asinhrona (item); } console.log("This will be printed after async operations :)") } |
Problems with array iteration methods
A method for iterating through arrays, such as “forEach()” cannot be used with async/await syntax. Using the “await” operator inside a method throws an error, and the error occurs because the “await” operator can only exist within an async function, and in this case it would be within the forEach() method.
|
1 2 3 4 5 |
async function obradaNizaPromisa (){ niz.forEach(item => { await asinhrona (item); }) } // IZBACUJE ERROR |
Even if within forEach() the anonymous callback function was defined as asynchronous, it would only help that it does not throw an error, but it would not give the expected result, because the “forEach” method does not “wait” for an asynchronous operation, but continues the execution of synchronous code.
|
1 2 3 4 5 6 |
async function obradaNizaPromisa (){ niz.forEach(async (item) => { await asinhrona (item); }) console.log("This is a synchronous action that will unfortunately be executed before the asynchronous ones") } |
Examples of using Async/Await syntax
This section describes the process of switching the syntax from “plain” promises to the new improved Async/Await syntax.
Individual asynchronous operations
a) Simple asynchronous operation
|
1 2 3 4 5 6 |
function asyncFunc() { return otherAsyncFunc() .catch(err => { console.error(err); }); } |
|
1 2 3 4 5 6 7 |
async function asyncFunc() { try { await otherAsyncFunc(); } catch (err) { console.error(err); } } |
b) XMLHttpRequest()
In this example they are shownsnippets for getting data in the form of JSON, in two ways: the first one using HMLHttpRequest() and Promise and the second one where the new Async/Await syntax is applied.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
function getJSON (url) { return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.onload = function() { if (xhr.status === 200) { var data = JSON.parse(xhr.responseText); resolve(data); } else { reject(new Error(xhr.statusText)); } }; xhr.onerror = () => reject(new Error("Network error")); xhr.send(); }); } getJSON('https://api.myjson.com/bins/erxi9') .then( function(data){ console.log(data); return "Done" }) |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
function getJSON (url) { return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.onload = function() { if (xhr.status === 200) { var data = JSON.parse(xhr.responseText); resolve(data); } else { reject(new Error(xhr.statusText)); } }; xhr.onerror = () => reject(new Error("Network error")); xhr.send(); }); } async function serializeJSON(url) { const data1 = await getJSON(url); return data1; } serializeJSON('https://api.myjson.com/bins/erxi9') .then( function(data){ console.log(data); return "Done" }) |
c) fetch()
|
1 2 3 4 5 6 |
fetch('/users.json') .then(response => response.json()) .then(json => { console.log(json); }) .catch(e => { console.log('error!'); }) |
|
1 2 3 4 5 6 7 8 9 10 |
async function getJson() { try { let response = await fetch('/users.json'); let json = await response.json(); console.log(json); } catch(e) { console.log('Error!', e); } } |
A series of sequential promises
|
1 2 3 4 5 6 7 8 9 10 |
function asyncFunc() { return otherAsyncFunc1() .then(result1 => { console.log(result1); return otherAsyncFunc2(); }) .then(result2 => { console.log(result2); }); } |
|
1 2 3 4 5 6 |
async function asyncFunc() { const result1 = await otherAsyncFunc1(); console.log(result1); const result2 = await otherAsyncFunc2(); console.log(result2); } |
A series of sequential interdependent promises
All the advantages and improvements brought by the new syntax can best be seen in this example. First, a series of linked promises with only promises (without Async/Await) is shown:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// Osnovni promise function doubleAfter2Seconds(x) { return new Promise(resolve => { setTimeout(() => { resolve(x * 2); }, 2000); }); } // Serializing multiple promises function serializePromise(x){ return new Promise(resolve => { doubleAfter2Seconds(x).then((a) => { doubleAfter2Seconds(a).then((b) => { doubleAfter2Seconds(b).then((c) => { resolve(x + a + b + c); }) }) }) }); } serializePromise(10).then((sum) => { console.log(sum); // 10+20+40+80=150 }); |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function doubleAfter2Seconds(x) { return new Promise(resolve => { setTimeout(() => { resolve(x * 2); }, 2000); }); } async function serializeAsync(x) { const a = await doubleAfter2Seconds(x); const b = await doubleAfter2Seconds(a); const c = await doubleAfter2Seconds(b); return x + a + b + c; } serializeAsync(10).then((sum) => { console.log(sum); // 10+20+40+80=150 }); |
A series of multiple parallel asynchronous operations
|
1 2 3 4 5 6 7 8 9 |
function asyncFunc() { return Promise.all([ otherAsyncFunc1(), otherAsyncFunc2(), ]) .then([result1, result2] => { console.log(result1, result2); }); } |
|
1 2 3 4 5 6 7 |
async function asyncFunc() { const [result1, result2] = await Promise.all([ otherAsyncFunc1(), otherAsyncFunc2(), ]); console.log(result1, result2); } |
