Content posted here with the permission of the author Pragati Garud, who is currently employed at Josh Software. Original post available here
As you know javascript is synchronous language. To deal with async operations javascript has provided three ways:
1] Promises
2] Async & await
3] Generator function
Promises: Whenever we ask for some async operation like fetching data from database, instead of waiting for that to get completed, javascript just returns you the promise, on that promise object you can add your success and error handler.
let promise = new Promise((resolve, reject) => { // do a thing, if everything is fine then resolve // otherwise reject }) //Success handler executed on promise resolve promise.then((data) => console.log(data)); //Error handler executed on promise reject promise.catch((error) => console.log(error));
if async operation that you requested is successfully completed then your success handler will get executed otherwise error handler will get executed.
Example: Suppose you want to fetch a list of users from database.(Note: I am assuming that you are aware about the `fetch` function and it’s error handling)
const fetchData = () => { fetch('https://jsonplaceholder.typicode.com/users') .then((response) => { console.log(response); }) .catch((error) => { console.log(error); }) } fetchData();
In above code we first requested for users list to server using fetch() and it’s a async operation, so we will get promise as a result of this operation and I added success and error handler on that promise. The response returned by the fetch needs to be converted in the json format.
const fetchData = () => { fetch('https://jsonplaceholder.typicode.com/users') .then((response) => { return response.json(); }) .then((data) => { console.log(data); }) .catch((error) => { console.log(error); }) } fetchData();
here, we called json() on the response to convert that into json, and it is also a async operation that results into a promise. so here from success handler of one promise I am returning another promise called Promise chaining.
So in case of promise chaining, code will look like little bit confusing. hence ES7 comes up with the async/await
Async/Await: async/await is just a syntactical sugar to promise. it provides you the way that you can write your async operations which will look like synchronous. The async function always returns you the promise, even if you return some non-promise value from your async function, javascript just wraps it inside promise and then returns.
async function f() { // await works only inside async functions let value = await promise; return 1; } async function f() { // await works only inside async functions let value = await promise; return Promise.resolve(1); }
await instructs the javascript that wait until provided operation gets finished. The same example that we saw using promises using async/await will be like:
const fetchData = async () => { try { let apiEndpoint = 'https://jsonplaceholder.typicode.com/users' let response = await fetch(apiEndpoint); let data = await response.json(); console.log(data); } catch (error) { console.log(error); } } fetchData();
here, I defined a async function and used await for my async operations, so javacsript will wait until it will get finished. if your async operation will get failed then await will throw the exception that’s why try catch is used.
Generator Function: It is a special kind of function which can stop it’s execution at a point and resume it’s execution from the same point later. Basically, it’s a function which can pause/resume on our demand.
function* generator() { // do something yield expression; }
* after function keyword denotes that it’s a generator function. Inside generator function yield will get used, this is the point where execution of the generator function will get stopped and It will yield an object { value: result, done: true/false }
to caller function. value contains the result of the expression given to yield and done indicates that collection on which you are iterating has finished or not(true/false).
Calling a generator function is different than our normal javascript function. Normal function just start it’s execution once we invoke that function. It follows Run to Completion
model. But, When we call generator function it does not start the execution of the function body, it just returns you the iterator object on which you can call next()
and throw()
method. When you call first time next()
method on the iterator object then it will start the execution of your function body.
Basic Example for generator:
function* generator() { yield 5+3; yield 7*2; } let it = generator(); // does not start execution of function body console.log(it.next()); // { value: 8, done: false } console.log(it.next()); // { value: 14, done: false } console.log(it.next()); // { value: undefined, done: true }
we can use generator function for async operation handling. we just pause at our async operation and resume generator when that async operation will be done.
Example: fetching list of users from server:
function* fetchData() { try { let apiEndpoint = 'https://jsonplaceholder.typicode.com/users' let response = yield fetch(apiEndpoint); let data = yield response.json(); console.log(data); } catch (error) { console.log(error); } } let it = fetchData(); // { value: promise returned by fetch(), done: false } let promise1 = it.next().value promise1.then((response) => { //resume generator by calling next() as your async operation is fulfilled now. // we can pass values to generator from here. e.g response is accessible here but we need that in the generator, passed value will gets assign to response variable of the generator. //{ value: promise returned json(), done: false } let promise2 = it.next(response).value promise2.then((data) => { it.next(data); }); });
Generator functions are fun and really a good concept. As Generator function provides power to caller to control the execution of the function, most of the libraries uses this concept to handle the async action.
Conclusion:
when to use which one ?
- Async/Await – when you want synchronous behaviour with async operations.
- Promises – when you want async nature. i.e. execution should continue while you wait for results.
- Generators – when you don’t want function to run to completion.