Promises, Generators and Observable in JavaScript
In this story we will look into different ways for performing asynchronous operation in JavaScript.
JS itself actually never had direct asynchronous mechanism, surprising isn’t it? JS runs in hosting environments like browsers, NodeJS. This environments provides mechanism to executing different JS code pieces over the time called Event Loop. It is nicely explained in this video.
Why do we need Asynchronous execution of code? Imagine if there is synchronous execution, in that case browser would freeze for the period of time some JS code is executing especially code like Ajax requests, delay execution, etc. User can not perform any activity — not even scrolling while it is being done. Also, it is necessary to execute some code at arbitrary times like when Ajax request is completed.
Callbacks
The most common mechanism used to achieve asynchronous execution is callback function. Callbacks are functions passed as parameter to another functions, these functions usually performs some asynchronous activity and the callback is run when the activity is completed. Example:
// Sample ajax demo
Ajax({ url, success: function successCallback(data) {} });
Thus, Callback is function which is executed when required activity is completed. Now, consider the situation you want to do Ajax request and based on the response you are calling appropriate callbacks:
function completeOrder(url, processData, complete) {
Ajax({
url,
success: function process(data) {
processData(data, complete);
}
}
});
}
As number and depth of callbacks increases, it becomes difficult to understand the code — the ‘callback hell’. This is one issue, another issue is complete control of execution is given to the function, what if callback is never called or called where/when it shouldn’t have been called. It is difficult to take corrective action if there is exception in deep stack of callbacks.
Promises
If the functions had mechanism to return something that will let us know when the task is completed then caller code can execute required code after it. This way it will solve problems with callbacks. This is exactly what Promise does. First, lets look at above code with promises:
function completeOrder(url, processData, complete) {
Ajax({ url })
.then(data => processData(data))
.then(complete());
}
Here multiple then functions are chained together — they are called one after. When Ajax is completed then first then is called and so on.
The then
function can take two function arguments —first for fulfillment and second for rejection. The promise once resolved it becomes immutable & can be then observed multiple times. Below is example of how to create new promise:
function getPromise(){
return new Promise(function(resolve, reject){
var validateResult = validate();
if (!validateResult.isValid){
reject(validateResult);
}
// do some work, it can be asynchronous
setTimeout(() => {
resolve('some data');
}, 1000);
});
}getPromise().then(x => console.log(x));
The promise is resolved/rejected only once and then then
function is called. If there are multiple call to reject/resolve only first is considered.
Generators
Generators are special functions with mechanism to input and output values using next
and yield
. Generators are functions which can be exited and later re-entered. Their context (variable bindings) will be saved across re-entrances. Consider the below sample:
function* sum(input) {
var result = input + (yield);
return result;
}var it = sum(2);// sum execution starts
it.next();var res = it.next(4);
res.value; //6
when first next
is called, generators start executing. It pauses when it encounters yield
statement. When we call next again, it resumes execution with value passed in next as result of yield. It is not required to pass value for yield statement always. Below is sample Ajax request with generator, promises is also used to demonstrate how promised based libraries can be used with generators:
function callAPI(name) {
var url = 'https://someAPI?name=' + name;
return new Promise(function(resolve, reject) {
Ajax({
url,
success: function(data) {
resolve(data);
}
});
});
}function* main() {
try {
var data = yield callAPI('Hello');
console.log('Data: ' + data);
} catch (err) {
console.error(err);
}
}var it = main();
var res = it.next();
res.value.then(data => it.next(data));
When it.next
is called, it will call API with hello
as name parameter. With second call for it.next
in then, the result data is yielded to data in main function.
The entire structure might look complicated, but if focus only on *main
function, we can see - we are calling API and getting result in data as if it were synchronous call. There is only addition of the yield
in it.
Observable
Observable represents the idea of an invokable collection of future values or events. There are wide range of operators in RXJS that helps in controlling the event flow and transforming the values and they are pure functions. Currently (2018), observable are not native in JS and it is available in RxJS library.
The main properties of observable are:
1. Lazily evaluated
2. Can be Synchronous or Asynchronous
3. Returns zero to multiple values
4. Can return values over the time
Below is Simple example of creating and using Observable. The subscribe method is required to start execution:
import { Observable } from 'rxjs';const observable = Observable.create(function(observer) {
// Get Value synchronously
observer.next(100);// Get Value Asynchronously
setTimeout(() => {
observer.next(200);
}, 1000);
});observable.subscribe({
next: value => console.log(value),
error: error => console.error(error),
complete: () => console.log('Done !')
});
In generators, we have used it.next
to pull next value. In observable, we just subscribe and it will automatically push new value when it is produced.
Also, it is easy to cancel subscription — by calling unsubscribe
on observable subscription. Example — below interval generates sequence of infinite numbers with delay specified. However, by calling unsubscribe after 4s, it will automatically cancel the further execution.
import { interval } from 'rxjs';const observable = interval(1000);
const subscription = observable.subscribe(x => console.log(x));setTimeout(() => {
subscription.unsubscribe();
}, 4000);
Below is incomplete snippet of how observable makes it easy to call API in angular HttpClient:
getComments(){
this.httpClient.get<Comments[]>(this.commentsUrl)
.subscribe(comments => this.comments = comments);
}
Thus, observable produces multiple values like generators while promise can either resolve or reject only one values. But it has powerful feature of promise to push value. Also, Observable has functionality to subscribe and unsubscribe to start and cancel the execution.
Thanks for reading, feel free to share and tap on clap button, if you enjoyed it.