What are the advantages of Promises over CPS and the Continuation Functor/Monad?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Promises provide significant advantages over Continuation-Passing Style (CPS) and the Continuation Functor/Monad: they flatten callback nesting into chainable .then() calls, centralize error handling with .catch(), guarantee single resolution (a Promise settles exactly once), and offer built-in concurrency primitives (Promise.all, Promise.race). While CPS and continuation monads are more general and composable in functional programming, Promises are more practical for everyday async programming — especially in JavaScript where they integrate with async/await syntax.
Quick Comparison
| Feature | CPS | Continuation Monad | Promises |
| Readability | Deep nesting | Requires monad knowledge | Flat chains, async/await |
| Error handling | Manual at every step | Via monad error handling | Centralized .catch() |
| Composition | Manual chaining | bind/>>= | .then() chaining |
| Single resolution | No guarantee | Depends on implementation | Guaranteed (settle once) |
| Concurrency | Manual | Manual | Promise.all, Promise.race |
| Language support | Any language | Functional languages | Native in JS, adopted widely |
CPS (Continuation-Passing Style)
In CPS, every async function takes a callback that receives the result:
Every callback must manually check for errors. Adding a step means adding another nesting level.
The Same Thing with Promises
The flat .then() chain replaces nesting with a linear pipeline. A single .catch() at the end handles errors from any step.
With async/await
async/await is syntactic sugar over Promises. It provides the readability of synchronous code with the non-blocking behavior of async operations.
Advantage 1: Flat Composition
Each .then() returns a new Promise, enabling flat chaining regardless of how many steps you add.
Advantage 2: Centralized Error Handling
Errors propagate down the Promise chain until a .catch() handles them — similar to synchronous try/catch.
Advantage 3: Single Resolution Guarantee
A Promise transitions from pending to either fulfilled or rejected exactly once. Subsequent calls to resolve or reject are silently ignored.
Advantage 4: Built-In Concurrency
Continuation Monad Comparison
The continuation monad is the theoretical foundation behind Promises:
Promises are a specialized, practical version of the continuation monad. The continuation monad is more general (works for any control flow), but Promises are more ergonomic for async operations.
Common Pitfalls
- CPS error handling discipline: In CPS, forgetting to check
errin any callback silently drops errors. Promises propagate errors automatically — you must actively ignore them (no.catch()), which linters can detect. - Promise constructor anti-pattern: Wrapping an existing Promise in
new Promise()is unnecessary:new Promise(resolve => resolve(fetch(url)))should be justfetch(url). Only usenew Promiseto wrap callback-based APIs. - Unhandled rejections: Unlike CPS where errors are silently lost, unhandled Promise rejections trigger warnings (Node.js) or errors (browsers). Always add
.catch()or use try/catch with async/await. - Continuation monad learning curve: The continuation monad requires understanding monadic composition (
bind,>>=), which has a steep learning curve compared to.then()chains. Promises trade some generality for accessibility. - Mixing CPS and Promises: Converting between CPS and Promises (with
util.promisifyor manual wrapping) is common but error-prone. Ensure the callback follows the(error, result)convention, or the conversion will not work correctly.
Summary
- Promises flatten callback nesting into linear
.then()chains - A single
.catch()handles errors from any step in the chain — no manual checking at each callback - Promises guarantee single resolution — a Promise settles exactly once, preventing double-callback bugs
- Built-in
Promise.all,Promise.race, andPromise.allSettledhandle concurrency without manual coordination async/awaitsyntax makes Promise code read like synchronous code- The continuation monad is more general but less practical — Promises are a specialized, ergonomic version for async programming

