Welcome to Part 9 of this review of the Pluralsight and Front End Masters course Advanced JavaScript by Kyle Simpson.
Kyle Simpson is Head of Curriculum for MakerSquare and an evangelist of the open web. He lives in Austin, Texas, and is passionate about all things JavaScript.
He’s written 8 books published by O’Reilly, including six books in the You Don’t Know JS series.
Kyle teaches JavaScript and has eight courses recorded by Front End Masters.
He’s a public speaker, and contributes to the world of OSS.
In this series:
Part 1 – Introduction
Part 2 – Scope
Part 3 – Lexical Scope
Part 4 – Block scoping
Part 5 – Dynamic Scope, Hoisting and this
Part 6 – Closure
Part 7 – Object Prototypes
Part 8 – Inheritance and OLOO
Part 9 – Async Patterns
In the earlier parts of this course we took a deep dive into the low level mechanics of JavaScript. Here we discuss one of the most essential higher level concepts: asynchronicity.
(Also see Kyle’s YDKJS book async & performance to learn this topic in even more depth)
Async Patterns
Callbacks
One reasons that asynchronous code is difficult to understand because our brains work synchronously.
Let’s look at a piece of trivial code and try to describe it in English:
setTimeout(function(){
console.log(“callback!”);
}, 1000);
Kyle’s description of how this works is:
I’m going to setup a timer for a thousand milliseconds from now, and then when that timeout fires, I’m going to print out the string “callback!”
This is a synchronous description of asynchronous code, glossing over the fact that it’s impossible to wait a thousand milliseconds. A more accurate, and awkward, asynchronous description is:
I’m going to setup a timer for a thousand milliseconds from now, and then I’m going to process some other work and I’ve got some other events handling. And somebody clicked a button in AJAX.
Oh, it’s been a thousand milliseconds: time to go print out this “callback!”
We like to think that we’re multi-taskers, but multitasking is just fast context switching.
Even activities such as breathing happen synchronously, at a subconscious level.
So to understand asynchronous JavaScript we need to express it in a way such that we can reason about it in a synchronous fashion.
Callbacks are one way that we do that. A callback is a continuation.
We are essentially splitting our program into two pieces are saying the second piece happens inside of the callback function.
setTimeout(function(){
console.log(“callback!”);
}, 1000)
Here we’re asking it to continue at some point a thousand milliseconds from now.
Callbacks work okay for these trivial examples with only one split. In real world code there’s many asynchronous steps:
setTimeout(function(){
console.log(“one”);
setTimeout(function(){
console.log(“two”);
setTimeout(function(){
console.log(“callback!”);
}, 1000);
}, 1000);
}, 1000);
A couple of names that this goes by is “callback hell” and “the pyramid of doom”
Kyle says callback hell has nothing to do with indentation. This code does exactly the same thing:
function one(cb) {
console.log(“one”);
setTimeout(cb,1000);
}function two(cb) {
console.log(“two”);
setTimeout(cb,1000);
}function three(cb) {
console.log(“three”);
}one(function(){
two(three);
});
This is called the continuation passing style. It’s nicer to read, but it’s just as susceptible to the problems of callback hell.
When we’re calling something that we don’t trust like a 3rd party library, we’re actually giving them an amazing amount of trust because we’ve just done Inversion of control.
We’re trusting that it will call our callback not too early, not too late, not too few times and not too many times. If there are parameters involved, we’re trusting that those will be passed correctly, etc.
Can solve inversion of control in a different way than with callbacks?
Solving Callback Problems
We first see the pattern of using separate callbacks:
function trySomething(ok,err) {
setTimeout(function(){
var num = Math.random();
if (num > 0.5) ok(num);
else err(num);
},1000);
}trySomething(
function(num){
console.log(“Success: ” + num);
},
function(num){
console.log(“Sorry: ” + num);
}
);
Kyle says this is even more implicit trust because we’re trusting that we’re only going to call one and not the other. It could call both the success and the failure!
Then there is “Node style” or as Kyle recommends “error-first style” code:
function trySomething(cb) {
setTimeout(function(){
var num = Math.random();
if (num > 0.5) cb(null, num);
else cb(“Too low!”);
},1000);
}trySomething(function(err,num){
if (err) {
console.log(err);
}
else {
console.log(“Number: ” + num)
}
});
We pass in a single callback, and we have an error function. The first parameter err represents any error.
The obvious disadvantage of this approach is all the if/else blocks. We should also consider what we might do if we received both an error and a success value?
Another approach is nested-callback tasks:
function getData(d,cb) {
setTimeout(function(){ cb(d); },1000);
}getData(10,function(num1){
var x = 1 + num1;
getData(30,function(num2){
var y = 1 + num2;
getData(
“Meaning of life: ” + (x + y),
function(answer){
console.log(answer);
// Meaning of life: 42
}
});
});
This has inversion of control trust issues just like the previous patterns.
Generators (yield)
This is new in ES6.
A common mis-assumption is that all code in a function runs before any other piece of code runs. It’s not true of generators: these can pause themselves in the middle of a function and be resumed later.
function* gen() {
console.log(“Hello”);
yield null;
console.log(“World”);
}var it = gen();
it.next(); // prints “Hello”
it.next(); // prints “World”
The generator function constructs and returns an iterator, and then we call the next method on it to run each next step.
yield is a two-way message passing mechanism: we can pass messages into our generator and receive messages back from it.
We see an code example showing use of this mechanism to calculate the number 42 and log “Meaning of life: 42”. This is another synchronous example, but we can also use generators as an async pattern:
function getData(d) {
setTimeout(function(){ run(d); },1000);
}var run = coroutine(function*(){
var x = 1 + (yield getData(10));
var y = 1 + (yield getData(30));
var answer = (yield getData(
“Meaning of life: ” + (x +y)
));
console.log(answer);
// Meaning of life: 42
});run();
Although this is asynchronous code, it looks like synchronous code.
Kyle says this is a glimpse of what they future of asynchronous programming in JavaScript looks like. He says generators and promises will be used together, and async functions are coming in ES7.
(A good book for learning ES6 is Getify’s YDKJS – ES6 & Beyond. And a good Frontend Masters course for learning ES6 is JS.Next: ES6 by Aaron Frost.)
Promises
A couple of metaphors:
- Ordering at a fast food restaurant. We get a receipt with an order number, and when our order is ready we exchange the receipt for our food. This is an asynchronous transaction, and the receipt represents a promise.
- Subscribing to an event that lets us know when it finishes. A “continuation event”.
Kyle talks about jQuery’s non-standard implementation of promises (Deferred).
In the latest version of jQuery, Deferred has been updated to be compatible with ES2015. So this is a good option.
If you don’t want to use jQuery, or another Promises library, you can use native promises because they’re part of the language now.
asynquence
Kyle feels that promises don’t go far enough with some of their abstractions, and that most people will use abstraction libraries on top of them.
When working with complex real world code, it’s tedious to create promises everything and manually chain them together.
This is why he wrote asynquence.
A couple of popular alternatives which he mentions are async and q.
Rob Conery covers the async library in the Registration module of his Node Application Patterns course.
asynquence stands for asynchronous sequences. And a sequence means automatically chained promises. Here’s an example:
ASQ()
.then(function(done){
setTimeout(done,1000);
})
.gate(
function(done){
setTimeout(done,1000);
},
function(){
setTimeout(done,1000);
}
)
.then(function(){
console.log(“2 seconds passed!”);
});
A gate is two or more things happening at the same time, with no requirement on which finishes first or last, only that we don’t move on until they’re all finished.
We also see another meaning of life code example. This one uses ASQ’s seq and waterfall methods.
Next there’s a generator plus promises example that uses the runner API.
Finally there’s an example of CSP-style generators: Communicating Serial Processes.
We take two or more generators and interleave their operation. It’s an advanced model for continuation style.
Quiz: Async Patterns
- What is “callback hell”? Why do callbacks suffer from “inversion of control”?
- How do you pause a generator? How do you resume it?
- What is a Promise? How does it solve inversion of control issues?
- How do we combine generators and promises for flow control?
Answers to all these questions are given in this clip in the course.
Exercise 5
Load up 3 separate files in parallel and print them out in the correct order. Kyle wrote a solution using nested callbacks, and the challenge is to rewrite it using more modern asynchronous patterns.
Exercise 5 Solution
Although this exercise sounds straight forward, the devil is in the details. Kyle runs through the complete solution here.
More async resources
Kyle has done a talk called syncing async that you might like to watch.
Also on Pluralsight
Wes Higbee has two courses on Asynchronous Programming in JavaScript, and I’ve alreay written up a review of the first of them: Reasoning About Asynchronous JavaScript
Another great resource for learning Async patterns is the Async Patterns module of Jonathan Mills’ JavaScript Best Practices course.
Ways that you can thank Getify
I hope that you agree with me that Getify has produced an amazing course. There a couple of easy win-win ways that you can thank him and also gain incredible value for yourself:
- Watch the course yourself, either on Pluralsight or on Frontend Masters
- Buy one or more books from the You Don’t Know JS series. Yes, you can read it online for free, but to get maximum value out of this series you’re going to want at least one paper copy to hand.
Every time a reader reads this blog post, getify doesn’t get diddly squat. But every time a course is watched or a book is bought a few cents are thrown his way.
Or if you are really generous you can become a patron.
You can also of course thank him without spending anything. He’s available on twitter.
Ways that you can thank me
Producing this series has involved a lot of work, and it’s entirely a labor of love for me. If you enjoyed it, please consider:
- Spreading the word on social media
- Following me on Twitter
- Hanging around on zombiecodekill a bit longer and seeing what other posts you might like
- Following this blog
- Listening to my Developer on Fire interview
This review has been for the penultimate course in the new JavaScript learning path on Pluralsight. The course is “Rapid ES6 Training” and this will be out in the next few days.
And Finally
Privilege Awareness is a topic that both myself and getify feel is very important. I believe that it can help us to make better decisions in many aspects of our lives.