Normally javascript is a synchronous language that executes the code block by order after hoisting (hoisting : var, functions are moved up to the top of code in declaration). Therefore a program waits for other function to finish and return until another function runs. This may be inefficient and cause a delay in the execution.
Therefore asynchronous programming provides you with APIs that allow you to run such tasks asynchronously. Asynchronous programming is a means of parallel programming in which a unit of work runs separately from the main application thread and notifies the calling thread of its completion, failure or progress.
When web app runs in a browser and it executes an intensive chunk of code without returning control to the browser, the browser can appear to be frozen : blocking
Therefore browser is blocked from continuing to handle user input and perform other tasks until the web app returns control of the processor.
This is because Javascript is single threaded
A thread is basically a single process that a program can use to complete tasks. Each thread can only do a single task at once.
However with multiple cores in computers, Programming languages that can support multiple threads can use multiple cores to complete multiple tasks simultaneously.
JavaScript is traditionally single-threaded. Even with multiple cores, you could only get it to run tasks on a single thread, called the main thread.
To solbe the inefficiency, initially a concept called web workers were introduced. These allow to send some of the JS processing off to seperate thread to prevent block. However this had 2 problems :
Therefore the concept of Asynchronous programming was introduced. Features like Promises allow you to set an operation running, and then wait until the result has returned before running another operation.
Synchronous Javascript : When code is executed, the result is returned as soon as the browser can do so.
Asynchronous Javascript : Program deals with codes that take a certain amount of time to get the required data.
Ex)
//Asynchronous callback
function printWithDelay(print, timeout) {
setTimeout(print, timeout);
}
//Give a delay of 2 seconds using function
printWithDelay(() => console.log('hello 1'), 2000);
//Synchronous callback
function printImmediately(print) {
print();
}
printImmediately(() => consosle.log('hello 2'));
The following example shows 2 functions.
printWithDelay
: The first function uses the setTimeout()
function to delay the execution of function (mimic the server system were data is received in random time)printImmediately
: Direct execution from the browser as it is a synchronous codeTherefore hello2
is printed first compared to hello1
due to the delay.
Another example shows an example to print 3 lists on the screen :
const posts = [
{title: 'Post One', body: 'This is post one'},
{title: 'Post Two', body: 'This is post two'},
];
function getPosts() {
//Give a delay of 1s before executing function
setTimeout(() => {
let output = '';
posts.forEach((post, index) => {
output += `${post.title}`;
});
console.log(output);
}, 1000);
}
function createPost(post) {
//Give a delay of 2s before executing function
setTimeout(() => {
posts.push(post);
}, 2000);
}
getPosts();
createPost({title : 'Post Three', body : 'This is post three'});
The following function has 2 functions getPosts()
and createPost()
which each has a delay of 1s and 2s. The code defines 2 objects initially and uses createPost()
to add another object. However in the screen, only 2 lists are shown. This is because getPosts()
takes a shorter time to execute and has already finished and shown result on the console while createPost()
is still executing. This would require asynchronous programming to fix the problem.
To use asynchronous programming in JS, there are 2 ways :
Async callbacks are functions that are specified as arguments when calling a function which will start executing code in the background. When the background code finishes running, it calls the callback function to let you know the work is done, or to let you know that error has happened.
For the previous example, using callbacks to solve the problem is possible by :
//getPosts() defined above (ignored for space convinience)
function createPost(post, callback) {
//Give a delay of 2s before executing function
setTimeout(() => {
posts.push(post);
//Execute the function that has been received in parameter
callback();
}, 2000);
}
createPost({title : 'Post Three', body : 'This is post three'}, getPosts);
The getPosts()
function has been passed as a callback function and therefore executes in the background till it reaches the result. Therefore the additional object is now able to be added in the array before being printed in console.
Callbacks are versatile
A following is an example regarding id inputs and password inputs. It uses callbacks to clarify if id and password is correct :
class UserStorage {
loginUser(id, password, onSuccess, onError) {
setTimeout(() => {
if(
(id === 'Yoonseo' && password === 'dream') ||
(id === 'jiwon' && password === 'hello')
) {
onSuccess(id);
} else {
onError(new Error('not found'));
}
}, 2000);
}
getRoles(user, onSuccess, onError) {
setTimeout(() => {
if(user === 'Yoonseo') {
onSuccess({name : 'yoonseo', role : 'CEO'});
} else {
onError(new Error('no access'));
}
}, 1000);
}
}
//Using too much callback
const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = prompt('enter your password');
userStorage.loginUser(
id,
password,
(user) => { //When user inputs correct input : onSuccess(id) callback function activated (pass id)
userStorage.getRoles(
user, //The user becomes the first parameter in callback function
(userWithRole) => { //check if user name is correct again and if so assign name and role using callback function
alert(`Hello ${userWithRole.name}, your role is ${userWithRole.role}`);
},
(error) => {
console.log(error);
})
},
(error) => { //When error in input, callbackfunction onError() with error object passed as parameter
console.log(error);
}
);
loginUser
in the main to input the user id and passwordid
and password
, if it is correct, a callback function getRoles()
is called and the id
is passed as a parametername
and role
name
and role
is printed out in the console and function finishes.loginUser
and getRoles()
, there is an error handling function that deals with errors causedHowever the following code is too complicated and has a low readability. Therefore a new concept called Promise
has been introduced
Promise
is a Javascript object for asynchronous operation. Promise
enbales the asynchronous code to look more like a synchronous program by making the codes more simpler.
Promise initially is an object that represents an intermediate state of an operation - In effect a promise that a result of some kind will be returned at some point in future.
It has 3 states as a property
Promise
has 2 types depending on the role it has to take
1) Producer : When new promise is created, the executor runs automatically
The producer takes a callback function that has 2 arguments
resolve
: When the async function has successfully finished the task and return datareject
: When an error has occured during the process of getting data2) Consumer : Use the data received from the producer by codes like then
, catch
, finally
//Producer
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Yoonseo');
reject(new Error('no network'));
}, 2000);
})
//Consumer
promise
.then(value => {
console.log(value);
})
.catch(error => {
console.log(error);
})
.finally(() => {
console.log('finsihed');
});
In the consumer, each code has a different role
then()
: Contain a callback function that will run if previous operation is successful(normally does error check in back-end).catch()
: Runs if any of the then()
blocks failfinally()
: Runs always regardless of whether the operation is successful or notAs mentioned previously, then()
is able to receive data and deal with it received from promise
, but also get multiple other promise
to be able to asyncrhonously deal with it.
const fetchNumber = new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000);
});
fetchNumber
.then(num => num*2)
.then(num => num*3)
.then(num => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(num-1), 1000);
});
})
.then((num) => console.log(num));
A series of .then()
has been chained together. Also there is an additional promise()
that has been instantiated in which the result is linked with the original promise
Note : each .then()
returns a newly generated promise object automatically, which can be used for chaining
Error can be handled using catch()
. The following example uses
const getHen = new Promise((resolve, reject) => {
setTimeout(()=>resolve('Chicken', 1000));
});
const getEgg = (hen) => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(`${hen} => Egg`), 1000);
//setTimeout(() => reject(`Error has occured!`), 1000);
});
};
const cook = ((egg) => {
return new Promise((resolve, reject) => {
//setTimeout(() => resolve(`${egg} => cook done`), 1000);
setTimeout(() => reject('Error!'), 1000);
})
});
getHen
.then(hen => getEgg(hen))
.catch(error => {
return 'Hamburger';
})
.then(egg => cook(egg))
.catch(error => {
return 'No food';
})
.then(meal => console.log(meal))
.catch(event => console.log(event));
resolve()
and reject()
setTimeout()
for each promise instantiation to mimic the server operationthen()
and catch()
is defined to deal with both success and failure cases.catch()
is defined to act as the default error operation : Generally noticing the user an error has occured.catch()
returns an alternative to allow operations to act atleast similar to main operations objectiveclass UserStorage {
loginStorage(id, password) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if(
(id === 'Yoonseo' && password === 'yooncerjiwon') ||
(id === 'yoonseo' && password === 'yooncerjiwon') ||
(id === 'you' && password === '123')
) {
resolve(id);
} else {
reject(new Error('not found'));
}
}, 2000);
});
}
getRoles (user) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if(user === 'yoonseo' || user === 'Yoonseo') {
resolve({name : 'Yoonseo', role : 'CEO'});
} else {
reject (new Error('cant find role'));
}
});
}, 1000);
}
}
const userStorage = new UserStorage();
const id = prompt('Enter your id');
const password = prompt('Enter password');
userStorage
.loginStorage(id,password)
.then(id => userStorage.getRoles(id))
.catch(error => console.log(error))
.then(user => alert(`Hello ${user.name}, you are the ${user.role}`))
.catch(error => console.log(error));
The overall structure mimics the structure of synchronous programming, making the code much more cleaner. Also better error handling can be done by using promise.
//Pass the URL of image to fetch from the network as parameter
fetch('/practice.jpeg')
.then(response => {
if(!response.ok) {
throw new Error(`HTTP error, status : ${response.status}`);
}
else {
return response.blob();
}
})
.then(blob => {
let objectURL = URL.createObjectURL(blob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
})
.catch(error => {
console.log(`There has been a problem with fetch operation : ${error.message}`);
});
The following operation uses fetch()
to input an image on the website. promise
is used to deal with the fetch API.
Note that the fetch()
API returns a promise object as a result and therefore additional instantiation of promise object is not required
blob
if it is successfulblob
is then added to HTMLcatch()
to check for any error during the processWhen we want to run some code only after multiple promises have all fulfilled, we would be able to use the Promise.all()
static method.
For example when we’re fetching information to dynamically populate a UI feature on our page with content, you would need to receive all the data and only then show the complete content, rather than displaying partial information.
//fetch multiple image
//Show all images at once when all of the fetch is done
//function to return the fetch promise based on URL input
function fetchAndDecode(url, type) {
//Add return to be able to directly return the final result (promise returned by blob() or text())
return fetch(url).then(response => {
if(!response.ok) {
throw new Error('HTTP error! status : '+ response.status);
}
//Various types of resources may exist
//return different type of response based on resource type
else {
if(type === 'text') {
return response.text();
}
else if(type === 'blob') {
return response.blob();
}
}
})
.catch(error => {
console.log(`Error has occured during operation for resource ${url}`);
})
}
//Call the function individually to begin process of fetching and decoding the images
//Individual call allows faster operation as each call is independent to each other
//They do not wait for completion of each fetch operation
let practice = fetchAndDecode('/practice.jpeg', 'blob');
let practice1 = fetchAndDecode('/practice1.jpeg', 'blob');
let practice2 = fetchAndDecode('/practice2.jpeg', 'blob');
//Promise.all -> ensures the code runs only if all of the promise calls are finished
//The resulted individual promises are passed as an array to parameter
Promise.all([practice, practice1, practice2]).then(values => {
console.log(values);
// Store each value returned from the promises in separate variables; create object URLs from the blobs
let objectURL1 = URL.createObjectURL(values[0]);
// Display the images in <img> elements
// Done only 1 image for convinience
let image1 = document.createElement('img');
image1.src = objectURL1;
document.body.appendChild(image1);
});
The following program ensures that the execution of adding images to html page is not done only until all of the 3 images have finished fetching
Promises are similar to callbacks but in a different syntax. Promises are essentially a returned object to which you attach callback functions, rather than having to pass callbacks into a function. The advantages are
.then()
operationsJavaScript is a synchronous, blocking, single-threaded language, in which only one operation can be in progress at a time. But web browsers define functions and APIs that allow us to register functions that should not be executed synchronously, and should instead be invoked asynchronously when some kind of event occurs (the passage of time, the user's interaction with the mouse, or the arrival of data over the network, for example). This means that you can let your code do several things at the same time without stopping or blocking your main thread.
Syntactic sugar built on top of promises that allows you to run asynchronous operations using syntax that's more like writing synchronous callback code.
The async
keyword is put infront of a function declaration to turn it into an async function. The syntax makes the function activate the async characteristics -> Their return values are guaranteed to be converted to promises
function fetchUser() {
return new Promise((resolve, reject) => {
resolve('Yoonseo');
});
}
async function fetchUser() {
return 'Yoonseo';
}
The first one returns a promise()
object instantiated by using the new
keyword. The second one does the same function as first one, however does not require an instantiation as the async
keyword automatiacally convert the returned value to a promise
object.
It is also available to create an async function expression :
//Both are actually the same written in different syntax
let hello = async function() {return 'hello'};
let hello = async () => "Hello";
The async
comes with the await
keyword. await
can be put in front of any async promise-based function to pause your code on that line until the promise fulfills, then return the resulting value.
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve,ms));
}
function getString() {
return delay(3000)
.then(() => 'string');
}
async function getString() {
await delay(3000);
return 'string';
}
The 2 string functions do the same function however written in different syntax. The use of await
is async
function discards the requirement of using .then
Use the try/catch
arround the logic. The catch()
deals with the resulted errors
Ex)
const findAndSaveUser = async (User) => {
try {
let user = await Users.findOne({});
user.name = 'zero';
user = await user.save();
user = await Users.findOne({gender :'m'});
} catch (error) {
console.log(error);
}
};
For a code that showss the relationship between a baby and grown up, asyncrhonous programming is required as baby must always be executed first, then grown up. Therefore control of flow is required :
async function getBaby() {
await delay(2000);
return 'baby';
}
async function getHuman() {
await delay(2000);
return 'Human';
}
function getPerson() {
return getBaby().then(baby => {
return getHuman().then(human => {console.log(`${baby} => ${human}`)});
});
}
async function getPerson() {
const baby = await getBaby();
const human = await getHuman();
return `${baby} => ${human}`
}
getPerson().then((result) => console.log(result));
The two getPerson()
does the same function however the async
function has a much more clear syntax by the use of await
.
However there is improvement that can be made for the async
function. This is because the getBaby()
and getHuman()
is independent to each other and therefore getHuman()
does not have to wait for the getBaby()
to finish executing within the getPerson()
.
Initially due to the await
, the overall process took 4s in total as getBaby()
had to finish inorder to getHuman()
to execute. With the call of each function storing them in seperate variables and connecting the variables back to main variable using await
allows the overall time to decrease for efficiency and keep the overall flow of coding.
async function getPerson() {
const babyPromise = getBaby();
const humanPromise = getHuman();
const baby = await babyPromise;
const human = await humanPromise;
return `${baby} => ${human}`
}
By storing the 3 Promise
objects in variables, it has the effect of setting off their associated processes all running simultaneously.
It is available to use synchronous try...catch
structure with async/await
. The catch() {}
block is passed an error object and specifies the function to take when an error has happened.
async function getPerson() {
try {
const baby = await getBaby();
const human = await getHuman();
return `${baby} => ${human}`
} catch(error) {
console.log(error);
}
}
The previous example using fetch()
to bring image to HTML page can be improved in readability by mixing promise
with async
and await
async function myFetch() {
let response = await fetch('/practice.jpeg');
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(error => console.log(error));
All of the .then()
blocks can be replaced by using the await
keyword before the method call, and then assign result to variable
It is possible to use the Promise.all()
to be able to get all the results returned into a variable in a way that looks like synchronous code.
The previous example of getting multiple images using fetch()
can be improved by
let values = await Promise.all([coffee, tea, description]);
By using await
here we are able to get all the results of the three promises returned into the values array, when they are all available.