I want to talk about something I built recently, but first I want to provide context so that it can be understood by a wider audience. I’ll cover years of background and try to explain terms so that this article can be understood by a novice JavaScript developer. Callback Hell JavaScript is asynchronous by nature, but what does that mean? An function is just one whose result is not ready when the function returns. You can think of calling an asynchronous function as scheduling some work to be done, which may or may not eventually yield some value in the future. How do we get that value? The general mechanism is to pass a function that will receive it as an argument, i.e. a . asynchronous callback Chaining asynchronous functions, such that the results of one feed into another by calling an asynchronous function from within a callback to another asynchronous function, can lead to what is called : callback hell Promises The first advancement came as a library solution: . Instead of accepting a callback, an asynchronous function returns a promise object. Eventually, the promise either with a value or with an error. Either way, it . To know when that happens, you can attach success and/or failure callbacks with the methods and , respectively. These methods return a new promise to which further callbacks can be attached. If a callback returns a value, it is passed to the next callback in the chain. If it throws an exception, it is passed to the next callback. If it returns a promise, it is inserted at that point in the chain. Promises provided relief by replacing nested callbacks with chained callbacks, which are generally considered easier to read: promises resolves rejects completes then catch ¹ then catch (I have switched the example to use which arrived in the language standard at the same time as promises. I want these examples to show the state-of-the-art as it evolved.) arrow functions Further, promises let us consolidate error handlers for multiple asynchronous functions, in the same way that multiple function calls can be handled by a single try-catch statement. Promises come with their own problems. We’re still using callbacks, they’re just chained instead of nested. The most popular way to share one asynchronous result between multiple callbacks is to save it to a variable kept outside the callbacks. Exceptions are handled with a callback instead of the familiar try-catch structure. Compared to synchronous code, it is still less readable. Generators are JavaScript functions that may multiple values before finally returning. They are defined in JavaScript with a special syntax: Generator functions yield function* Calling a generator function does not start execution of the function. Instead, it returns a that represents the function call. You may step through the function by calling its method. The first time you call , it executes the function from its beginning to its first , returning the value that was yielded. The second time you call , it re-enters the function at the point where it yielded, replaces the yield expression with whatever argument you passed to , and continues to the next . This process is repeated each time you call , until the function finally returns or throws ( ): generator object next next yield ² next next yield next REPL Instead of , you may call to throw an exception from the yield expression. A try-catch block within the generator function can catch it, but if it is uncaught, it will pass up the call stack to the scope where you called ( ): next throw throw REPL There are a few related terms you may encounter when studying generators. The way the stepping code (by calling methods and ) and the generator (with , , and ) pass control back and forth between each other is called . This “bouncing” is why the stepping code is called a . Lastly, generators are a type of . next throw yield throw return cooperative multitasking trampoline coroutine Imagine if we write a generator that yields promises. We can pair it with a special trampoline that intercepts each yielded promise and adds success and failure callbacks that pass their argument back to the generator by calling either or , respectively. The trampoline itself returns a promise that resolves with the return value of the generator (or rejects with its only uncaught exception). This lets us write asynchronous code in a synchronous way: next throw Async Functions This pattern proved so popular that it was enshrined in the language with native syntax as . The only differences are that becomes and becomes : async functions async(function* (...) {...}) async function (...) {...} yield await Footnotes Actually, accepts both success and failure callbacks, but both are optional. accepts just the failure callback, and is the same as calling with no success callback. ¹ then catch then Actually, returns a structure like . The field indicates whether the value come from a return statement ( ) or a yield expression ( ). ² next { value: any, done: bool } done true false