# JavaScript Async Programming: From Callbacks to Async/Await

> Originally written in 2023. Content may vary slightly across newer versions.

JavaScript's async story didn't start with `async/await` — it evolved through several patterns, each solving problems the previous one couldn't. This article traces that evolution from callbacks all the way to async functions.

---

#### Synchronous vs. asynchronous

JavaScript is single-threaded — code executes line by line within the call stack. Unless the previous task completes, the next one waits. This is simple to reason about, but if one task runs indefinitely, everything else is blocked and the browser appears frozen.

JavaScript's asynchronous paradigm addresses this. Instead of running in one go, an async task is split into stages with callbacks. HTTP requests are a classic example: once a request is sent, the response won't come back immediately, so a callback handles the result when it does.

---

#### Callbacks

The oldest solution. Given two functions `f1` and `f2` where `f2` depends on `f1`'s result:
```javascript
f1()
f2()
```

If `f1` takes a long time, refactor `f2` as `f1`'s callback:
```javascript
function f1(callback) {
  setTimeout(function () {
    // f1's code
    callback()
  }, 2000)
}

f1(f2)
```

> **Pro:** Simple to understand and implement.
> **Con:** As logic grows, callbacks become deeply nested, hard to read, and tightly coupled.

---

#### Event-driven programming

Tasks execute based on whether a specific event fires.
```javascript
f1.on('done', f2)

function f1() {
  setTimeout(function () {
    // f1's code
    f1.trigger('done')
  }, 2000)
}
```

> **Pro:** Easy to decouple; works well with modularisation.
> **Con:** The overall logic flow becomes harder to follow as the application grows.

---

#### Publish/Subscribe

Treat events as signals. A signal centre publishes when a task completes; other tasks subscribe to act on it — also known as the observer pattern.

Using [Ben Alman's Tiny Pub/Sub](https://gist.github.com/cowboy/661855):
```javascript
jQuery.subscribe('done', f2)

function f1() {
  setTimeout(function () {
    // f1's code
    jQuery.publish('done')
  }, 2000)
}

jQuery.unsubscribe('done', f2)
```

> **Pro:** Similar to event-driven, but the signal centre gives a clearer picture of what's happening across the application.

---

#### Promises

Proposed by CommonJS as a standardised async solution. Every async task returns a Promise with a `then` method:
```javascript
f1().then(f2).then(f3)
```

Rewrite `f1` with a deferred:
```javascript
function f1() {
  var dfd = $.Deferred()
  setTimeout(function () {
    // f1's code
    dfd.resolve()
  }, 2000)
  return dfd.promise()
}
```

Handle errors:
```javascript
f1().then(f2).fail(f3)
```

> **Pro:** Callbacks are chained rather than nested. If a callback is attached after a task completes, it fires immediately — no risk of missing an event.

---

#### Generators

Introduced in ES6, generators can pause and resume execution using `yield`. They make async code read almost like synchronous code:
```javascript
function* gen() {
  var url = 'https://api.github.com/users/github'
  var result = yield fetch(url)
  console.log(result.bio)
}
```

Executing it manually:
```javascript
var g = gen()
var result = g.next()

result.value
  .then(function(data) { return data.json() })
  .then(function(data) { g.next(data) })
```

Generators also support two-way data flow and external error handling via `.throw()`:
```javascript
function* gen(x) {
  try {
    var y = yield x + 2
  } catch (e) {
    console.log(e)
  }
  return y
}

var g = gen(1)
g.next()
g.throw('error!') // error!
```

> **Con:** The two-phase execution pattern can be confusing, and generators require an external executor (like the `co` library) to run automatically.

---

#### Async/Await

Introduced in ES7, async functions are syntactic sugar for generators — and considered the ultimate solution to async programming.
```javascript
var asyncReadFile = async function () {
  var f1 = await readFile('/etc/fstab')
  var f2 = await readFile('/etc/shells')
  console.log(f1.toString())
  console.log(f2.toString())
}
```

Three improvements over generators:

1. **Built-in executor** — no `co` library needed; async functions run like regular functions.
2. **Better semantics** — `async` and `await` are self-explanatory compared to `*` and `yield`.
3. **Better adaptability** — `await` accepts both Promises and primitive values.

Async functions always return a Promise. Use `try...catch` for error handling:
```javascript
async function main() {
  try {
    const val1 = await firstStep()
    const val2 = await secondStep(val1)
    const val3 = await thirdStep(val1, val2)
    console.log('Final: ', val3)
  } catch (err) {
    console.log(err)
  }
}
```

Run independent operations in parallel with `Promise.all`:
```javascript
// slow — sequential
let foo = await getFoo()
let bar = await getBar()

// fast — parallel
let [foo, bar] = await Promise.all([getFoo(), getBar()])
```

---

#### Summary

| Pattern | Readability | Error handling | Coupling |
|---|---|---|---|
| Callbacks | Low | Manual | High |
| Event-driven | Medium | Manual | Medium |
| Pub/Sub | Medium | Manual | Low |
| Promises | High | `.catch()` | Low |
| Generators | High | `try/catch` | Low |
| Async/Await | Highest | `try/catch` | Low |

*References: [Async JavaScript](http://www.ruanyifeng.com/blog/2012/12/asynchronous＿javascript.html) · [Generator](http://www.ruanyifeng.com/blog/2015/04/generator.html) · [Async](http://www.ruanyifeng.com/blog/2015/05/async.html)*


