When things can happen independently of the programs main thread, we're talking about asynchronicity. By default JavaScript is a synchronous single threaded language which means it cannot run multiple threads parallelly. Your code will be executed from top to bottom, one after another. But what if you need to do some heavy computation on client side or just to wait for a long server response? The UI freezes until it's done, no clicks, no scrolling, no animations.
JavaScript (as it's name suggests) is a high-level, interpreted scripting language running in a browser by it's engine. For more information about engines, here is Geckos and V8s homepage. Browser provides features that can handle asynchron functionality. Web APIs, the event loop, the task queue are not part of the JavaScript engine. For deeper understanding of how event loop works check this great video.
Callbacks
A callback is a function which is passed to another function as a parameter. The simplest example for a callback is handling a button click. You need to listen for the click event, and when it happens, the browser will exectute the given function (the callback).
const button = document.getElementById('button');
const myCallback = () => alert('Click happened');
button.addEventListener('click', myCallback);
This way you can handle asynchronous server requests as well.
const request = new XMLHttpRequest();
const myCallback = event => console.log(event.target.response);
request.addEventListener('load', myCallback);
request.open('GET', 'http://www.example.org/example.txt');
request.send();
Callbacks are good for simple cases like handling a button click. The pain starts when you need to nest callbacks, and wrap logics into. It's called "Callback Hell" or "The Pyramid of Doom". For example let's wait for page load, then listen for the button click and when the button was cliked do a server request, then log it to the console.
window.addEventListener('load', () => {
document.getElementById('button').addEventListener('click', () => {
const request = new XMLHttpRequest();
request.addEventListener('load', (event) => {
console.log(event.target.response);
});
request.open('GET', 'http://www.example.org/example.txt');
request.send();
});
});
Promises
In ES6 there is a new feature called Promise. It's an object representing the eventual completion or failure of an asynchronous operation. It's constructor waits an exectutor function with parameters "resolve" and "reject". You can use the "then" method as fulfillment and rejection handler, "catch" for handle only rejection and "finally" for run code when promise is done. For example let's wrap a timeout into promise.
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Hello from promise');
}, 2000);
});
myPromise.then(returnedValue => console.log(returnedValue));
Promises are handy when you need to nest asynchronous parts but want avoid the Callback Hell. In this example I'll use Fetch API which returns a Promise. Let's create a snippet with a timeout. After that do a server request and then log the data out.
const timeout = () => new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Hello from promise');
}, 2000);
});
const request = () => fetch('http://www.example.org/example.txt')
timeout()
.then(request)
.then(response => response.json())
.then(data => console.log(data));
Async/Await
ES7 brings async and await syntax which are just syntactic sugar over Promises. "await" can only be used inside an async function. With them you can wait for promises with a very clear readable syntax. Let's refactor the code from above with async/await.
const timeout = () => new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Hello from promise');
}, 2000);
});
const request = async () =>
await fetch('http://www.example.org/example.txt');
await timeout();
const request = await request();
console.log(request.json());
Top comments (0)