Asynchronicity
The asynchronous nature of JS is generally regarded as a good thing. It means that things that don't resolve right away, but aren't processor intensive, won't block other code. If something needs to wait for an external event to occur (like a response from a server, or an event on the DOM), it won't prevent other things from happening. This all happens on the same thread, eliminating the need to allocate resources for other threads, and the associated issues with sharing those resources.
Callbacks
The ability to pass functions around like any other data structure is a key feature of functional programming and JavaScript, and arguably the most powerful and important concepts in modern programming. Asynchronous programming originally relied entirely on higher order functions through callbacks to execute some action after some other action was completed.
Examples:
var element = document.querySelector('#thing');
//listening for clicks. Will call callback function when clicked with event passed in.
element.addEventListener('click', function(event) {
console.log(event.target.innerHTML);
}
//making and network request. When response from server is received,
// callback will be called with error or response passed in.
xhrRequest('http://api.websitename.com/v1/users/47', function(err, user) {
if (err) {
throw new Error(err);
} else {
doSomethingWithUser(user);
}
}
//waiting for 4000 milliseconds (4 seconds). When time is up, will call function.
setTimeout(function(){
console.log("I've waited and now I'm ready")
}, 4000);
Promises
The main issue with callbacks is that you often want to execute some other asynchronous action when it's complete, and possibly another asynchronous action when that's complete (and so on). This makes it hard to reason about what your code is doing.
Promises were introduced to JS to help ameliorate those issues. They represent the eventual result of an async operation. According to the A+ standard, promises have three states (pending, fulfilled, rejected), and they must provide a then method,. "Then" accepts two arguments (onFulfilled, onRejected), and it must return another promise so that it can be called multiple times (chained) on a promise. The flow of the syntax makes it easier to follow and reason about.
Examples:
//creating a promise function
function clickSelector(element) {
return new Promise(function(resolve, reject) {
element.addEventListener('click', function(event) {
resolve(event)
}
}
}
var element = document.querySelector('#thing');
var selector = clickSelector(element);
selector.then(function(event) {
console.log(event.target.innerHTML)
}
//an http request using the builtin fetch function
fetch('http://api.websitename.com/v1/users/')
.then(function({response: {users}}) {
return fetch('http://api.websitename.com/v1/users/' + users[3].id);
})
.then(function({response: {user}}) {
doThingWithUser(user);
})
.catch(err) {
throw new Error(err);
};
Async/Await (ES2017)
async functions still lean of promises to provide asynchronicity, but provide an even cleaner syntax and allows code to more closely resemble synchronous code.
async function getUser(number) {
try {
var {response: {users}} = await fetch('http://api.websitename.com/v1/users/');
var {response: {user}} = await fetch('http://api.websitename.com/v1/users.' + users[number].id);
return user;
} catch (err) {
throw new Error(err);
}
}