Skip to content

Monad:为什么 Promise 是 Monad?

——flatMapthen)满足左单位律、右单位律、结合律

“Monad 就像洋葱:
你以为剥开一层是核心,
结果发现是另一层 then。”

一、问题:嵌套的函子(Functor)灾难

我们已知 Promise 是函子(Functor),支持 map

js
Promise.resolve(5)
  .then(x => x * 2) // Promise{10}

但问题来了:如果 map 的函数也返回一个 Promise

js
const fetchUser = id => fetch(`/api/users/${id}`).then(r => r.json());
const fetchPosts = user => fetch(`/api/posts?uid=${user.id}`).then(r => r.json());

// 尝试链式调用
Promise.resolve(1)
  .then(fetchUser)     // → Promise{ Promise{user} }
  .then(fetchPosts);   // ❌ 失败!收到的是 Promise,不是 user

结果是 Promise<Promise<Posts>> —— 嵌套了!

我们需要一种方式“压平”嵌套,这就是 Monad 的使命。

二、Monad 的核心:chain(aka flatMap, bind, then

函子(Functor):

js
F.map(f) // f: A → B → F<B>

单子(Monad):

js
M.chain(f) // f: A → M<B> → M<B>

关键区别:

  • map:函数返回普通值
  • chain:函数返回另一个容器(M<B>)
  • chain 会自动“解包”并合并容器,避免嵌套

Promise.then 就是 chain

js
Promise.resolve(1)
  .then(fetchUser)    // fetchUser 返回 Promise<User>
  .then(fetchPosts)   // ✅ 自动解包,收到的是 User
  .then(posts => console.log(posts));

then 不仅映射,还扁平化结果,所以 PromiseMonad

三、Monad 的三大定律

要称为 Monad,必须满足三个数学定律。

1. 左单位律(Left Identity)

M.of(a).chain(f) ≡ f(a)

js
// 左边
Promise.resolve(5).then(x => Promise.resolve(x * 2))
// → Promise{10}

// 右边
(x => Promise.resolve(x * 2))(5)
// → Promise{10}

// 相等!

“先提升再链” ≡ “直接调用函数”

2. 右单位律(Right Identity)

m.chain(M.of) ≡ m

js
const m = Promise.resolve(42);

// 左边
m.then(Promise.resolve) // Promise{42}

// 右边
m // Promise{42}

// 相等!

“链上提升函数”不改变原值

3. 结合律(Associativity)

m.chain(f).chain(g) ≡ m.chain(x => f(x).chain(g))

js
const m = Promise.resolve(5);

const f = x => Promise.resolve(x + 1);
const g = x => Promise.resolve(x * 2);

// 左边:先 f 后 g
m.then(f).then(g) 
// 5 → 6 → 12

// 右边:f 和 g 组合后再 chain
m.then(x => f(x).then(g))
// 5 → (6 → 12) → 12

// 结果相同

这保证了链式调用的可重组性

四、为什么 Monad 如此强大?

1. 处理异步依赖(串行)

js
getUser(1)
  .then(profile)
  .then(save)
  .then(notify);

每个步骤依赖前一个结果,Monad 让你像写同步代码一样组合异步操作。

2. 统一错误处理

js
fetch('/data')
  .then(parse)
  .then(process)
  .catch(handleError)
  .then(finalize);

失败时自动跳过后续 then,集中处理。

3. 可组合的副作用

js
const dbOp = () => 
  connect()
    .then(insert)
    .then(commit)
    .catch(rollback);

将复杂的副作用封装成可复用的 Monad 链。

五、其他常见的 Monad

1. Array 是 Monad

js
[1,2].flatMap(x => [x, x*2]) // [1,2,2,4]

flatMapchainArray.ofof

2. Maybe 是 Monad

js
Maybe.of(5)
  .chain(x => x > 0 ? Maybe.of(x*2) : Maybe.of(null))

避免空值,优雅失败。

3. Either 是 Monad

js
Either.of(5)
  .chain(x => x > 0 ? Right(x*2) : Left('invalid'))

用于业务逻辑错误处理。

六、Monad 在工程中的意义

更好的异步流控制

  • 替代回调地狱
  • async/await 更底层(async/await 是 Monad 语法糖)

可预测的错误传播

  • 错误自动沿链传播
  • 集中处理,避免重复 try/catch

函数式架构基石

  • Redux-Saga 使用 Generator + Monad 思想
  • RxJS Observable 是 Monad

七、async/await 是 Monad 的语法糖

js
async function process() {
  const user = await getUser(1);
  const posts = await getPosts(user.id);
  return { user, posts };
}

等价于:

js
getUser(1)
  .then(user => getPosts(user.id))
  .then(posts => ({ user, posts }));

await 解包 Promiseasync 函数自动返回 Promise —— 完美符合 Monad 行为。

结语:Monad 是“可组合的计算上下文”

它让你在容器中进行复杂的、依赖性的操作,而无需手动解包。

Promise 之所以强大,
不是因为它异步,
而是因为它是一个符合数学定律的 Monad

当你调用 then
你不是在“注册回调”,
你是在构造一个从 AB 的可组合、可推理的计算管道

这,就是函数式编程的深水区。