技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTJS

Promise 的 then 是如何实现链式调用的

所属馆藏
JavaScript
文件格式
Markdown
原始路径
JavaScript/51-Promise then是如何实现链式调用的
本文目录11 个章节
  1. 一、为什么 then 能链式调用
  2. 二、异步返回值如何传到下一个 then
  3. 三、回调队列和多个监听者
  4. 四、处理函数不是函数时的值穿透
  5. 五、Promise Resolution Procedure
  6. 六、一个可运行的最小 Promise 实现
  7. 七、原文的链式调用示例(现代写法)
  8. 八、Promise/A+ 与 ECMAScript Promise 的关系
  9. 九、常见错误
  10. 十、总结
  11. 参考资料

Promise 的 then 是如何实现链式调用的

Category(分类): JavaScript Status: 已整理(2026)

本文保留原文“then 返回新 Promise、回调队列、异步结果传递、多个 then 监听同一个 Promise、Promise/A+ 解析过程和手写实现”的主线。原文中的抓取格式标记、instanceof MyPromise 识别 thenable、同步执行回调、失败处理和状态拼写已修正,并补充标准 Promise 与 Promise/A+ 的边界。

原文:Promise then 是如何实现链式调用的

一、为什么 then 能链式调用

then 不会返回当前 Promise,而是立即返回一个新的 Promise。前一个 Promise 的处理函数执行后,其返回值会决定新 Promise 的状态:

  • 返回普通值:新 Promise fulfilled,值就是返回值;
  • 没有返回值:新 Promise fulfilled,值为 undefined
  • 抛出异常:新 Promise rejected,原因是该异常;
  • 返回 Promise 或 thenable:新 Promise 采用它最终的状态和值。
const result = Promise.resolve(1)
  .then(value => value + 1)
  .then(value => value * 2)

result.then(value => console.log(value)) // 4

这里每次 then 都返回下一环,而不是把同一个对象返回给自己:

const first = Promise.resolve(1)
const second = first.then(value => value + 1)

console.log(first === second) // false

二、异步返回值如何传到下一个 then

原文的示例等待两个定时器,最终约两秒后输出 2

const result = Promise.resolve(1)
  .then(value => {
    return new Promise(resolve => {
      setTimeout(() => resolve(value + 1), 1000)
    })
  })
  .then(value => {
    console.log(value) // 2,约 1 秒后
    return value
  })

如果第一个处理函数返回一个 pending Promise,第二个 then 不会立即收到 pending Promise,而是等它 fulfilled 或 rejected 后再继续:

Promise.resolve('start')
  .then(value => new Promise(resolve => {
    setTimeout(() => resolve(`${value} -> done`), 100)
  }))
  .then(console.log) // start -> done

这不是简单的“把回调放到下一个数组”就能完成的,还需要实现 Promise Resolution Procedure(Promise 解析过程),以处理普通值、Promise、thenable、异常和循环引用。

Promise 链式调用原文图示

图片来源:原始图片

三、回调队列和多个监听者

3.1 同一个 Promise 可以注册多个 then

const promise = new Promise(resolve => {
  setTimeout(() => resolve(1), 100)
})

promise.then(value => console.log('first:', value))
promise.then(value => console.log('second:', value))

两个回调都注册在同一个 Promise 上,按照注册顺序执行;它们各自开启独立的后续链:

const source = Promise.resolve(1)

const chainA = source.then(value => value + 1)
const chainB = source.then(value => value + 10)

Promise.all([chainA, chainB]).then(console.log) // [2, 11]

chainA 不会等待 chainBchainB 也不会等待 chainA。只有写在同一条链上的 then 才会依次等待上一个处理结果。

Promise 回调队列原文图示

图片来源:原始图片

3.2 同步注册,异步执行

then 的处理函数即使面对已经 fulfilled 的 Promise,也会异步执行:

const order = []

Promise.resolve().then(() => order.push('then'))
order.push('sync')

setTimeout(() => {
  console.log(order) // ['sync', 'then']
}, 0)

标准 Promise 的处理函数会排入 Promise jobs/microtasks;浏览器和 Node.js 对宿主任务的调度细节不同,但不能把 then 回调当作当前调用栈中的同步函数。

四、处理函数不是函数时的值穿透

then 的两个参数都可省略。如果 onFulfilled 不是函数,默认使用 identity 函数;如果 onRejected 不是函数,默认使用 thrower 函数:

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(value => console.log(value)) // 1

错误会继续向后传递,直到遇到真正的拒绝处理函数:

Promise.reject(new Error('failed'))
  .then(2, 3)
  .catch(error => console.log(error.message)) // failed

这也是 catch(onRejected) 可以实现为 then(undefined, onRejected) 的原因:

Promise.prototype.catch = function (onRejected) {
  return this.then(undefined, onRejected)
}

原生 Promise 的 catch 已由运行时提供,不要覆盖内置原型;上面的代码只用于说明关系。

五、Promise Resolution Procedure

可以把新 Promise 的解析过程简化成以下规则:

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Promise 自引用'))
  }

  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    let called = false

    try {
      const then = x.then

      if (typeof then === 'function') {
        then.call(
          x,
          value => {
            if (called) return
            called = true
            resolvePromise(promise2, value, resolve, reject)
          },
          reason => {
            if (called) return
            called = true
            reject(reason)
          }
        )
      } else {
        resolve(x)
      }
    } catch (error) {
      if (called) return
      called = true
      reject(error)
    }
    return
  }

  resolve(x)
}

这段代码展示的是核心思想,不是完整的原生 Promise 实现:

  1. 不能让新 Promise 解析为自己;
  2. 只要返回值是对象或函数,就读取一次 then
  3. then 是函数时,把它当作 thenable 调用;
  4. thenable 只能以第一次 resolve/reject 为准;
  5. thenable 的成功值还要递归解析;
  6. 读取 then、调用 then 或处理函数抛错时要拒绝新 Promise;
  7. 普通值直接 fulfilled。

不能只用 x instanceof MyPromise 判断,因为其他 Realm 的 Promise、原生 Promise 和用户自定义 thenable 都可能不是当前类的实例。Promise 互操作依靠的是 thenable 协议。

六、一个可运行的最小 Promise 实现

下面的 TinyPromisequeueMicrotask 模拟标准 Promise 的异步回调,用构造器的 resolve 逻辑实现 thenable 采纳。它用于学习,不是生产级 Promise polyfill,也没有覆盖所有宿主和规范边界。

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class TinyPromise {
  constructor(executor) {
    this.status = PENDING
    this.value = undefined
    this.reason = undefined
    this.handlers = []

    let alreadyResolved = false

    const fulfill = value => {
      if (this.status !== PENDING) return
      this.status = FULFILLED
      this.value = value
      this.flush()
    }

    const rejectInternal = reason => {
      if (this.status !== PENDING) return
      this.status = REJECTED
      this.reason = reason
      this.flush()
    }

    const resolveValue = value => {
      if (value === this) {
        rejectInternal(new TypeError('Promise cannot resolve to itself'))
        return
      }

      if (value !== null &&
        (typeof value === 'object' || typeof value === 'function')) {
        let then
        try {
          then = value.then
        } catch (error) {
          rejectInternal(error)
          return
        }

        if (typeof then === 'function') {
          let called = false
          try {
            then.call(
              value,
              nextValue => {
                if (called) return
                called = true
                resolveValue(nextValue)
              },
              reason => {
                if (called) return
                called = true
                rejectInternal(reason)
              }
            )
          } catch (error) {
            if (!called) {
              called = true
              rejectInternal(error)
            }
          }
          return
        }
      }

      fulfill(value)
    }

    const resolve = value => {
      if (alreadyResolved) return
      alreadyResolved = true
      resolveValue(value)
    }

    const reject = reason => {
      if (alreadyResolved) return
      alreadyResolved = true
      rejectInternal(reason)
    }

    try {
      executor(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }

  flush() {
    const handlers = this.handlers.splice(0)
    for (const handler of handlers) {
      queueMicrotask(handler)
    }
  }

  then(onFulfilled, onRejected) {
    const fulfilled = typeof onFulfilled === 'function'
      ? onFulfilled
      : value => value
    const rejected = typeof onRejected === 'function'
      ? onRejected
      : reason => { throw reason }

    let resolveNext
    let rejectNext
    const nextPromise = new TinyPromise((resolve, reject) => {
      resolveNext = resolve
      rejectNext = reject
    })

    const run = () => {
      try {
        const callback = this.status === FULFILLED ? fulfilled : rejected
        const input = this.status === FULFILLED ? this.value : this.reason
        resolveNext(callback(input))
      } catch (error) {
        rejectNext(error)
      }
    }

    if (this.status === PENDING) {
      this.handlers.push(run)
    } else {
      queueMicrotask(run)
    }

    return nextPromise
  }

  catch(onRejected) {
    return this.then(undefined, onRejected)
  }

  finally(onFinally) {
    if (typeof onFinally !== 'function') return this.then()

    return this.then(
      value => TinyPromise.resolve(onFinally()).then(() => value),
      reason => TinyPromise.resolve(onFinally()).then(() => {
        throw reason
      })
    )
  }

  static resolve(value) {
    return value instanceof TinyPromise
      ? value
      : new TinyPromise(resolve => resolve(value))
  }

  static reject(reason) {
    return new TinyPromise((resolve, reject) => reject(reason))
  }

  static all(iterable) {
    const values = Array.from(iterable)

    return new TinyPromise((resolve, reject) => {
      if (values.length === 0) {
        resolve([])
        return
      }

      const result = new Array(values.length)
      let remaining = values.length

      values.forEach((value, index) => {
        TinyPromise.resolve(value).then(item => {
          result[index] = item
          remaining -= 1
          if (remaining === 0) resolve(result)
        }, reject)
      })
    })
  }

  static race(iterable) {
    return new TinyPromise((resolve, reject) => {
      for (const value of iterable) {
        TinyPromise.resolve(value).then(resolve, reject)
      }
    })
  }
}

6.1 使用最小实现

const result = new TinyPromise(resolve => {
  setTimeout(() => resolve(1), 10)
})
  .then(value => value + 1)
  .then(value => console.log(value)) // 2

TinyPromise.all([TinyPromise.resolve(1), 2])
  .then(values => console.log(values)) // [1, 2]

这里有几个容易漏掉的点:

  • 构造器执行器是同步调用的,但 then 处理函数异步执行;
  • resolve 不能只设置 fulfilled,因为它还需要采纳 thenable;
  • resolve/reject 的第一次调用胜出;
  • then 返回的新 Promise 需要拿到处理函数的结果;
  • 处理函数返回的新 Promise 也需要被解析;
  • 空数组的 all([]) 应立即 fulfilled 为 []
  • 空 iterable 的 race([]) 保持 pending;
  • finally 不改变原来的值或拒绝原因,除非它自身抛错或返回 rejected Promise。

七、原文的链式调用示例(现代写法)

const start = Date.now()

const chain = new TinyPromise(resolve => {
  console.log('executor') // 同步执行
  setTimeout(() => resolve(1), 1000)
})
  .then(value => new TinyPromise(resolve => {
    setTimeout(() => resolve(value + 1), 1000)
  }))
  .then(value => {
    console.log(value, Date.now() - start) // 2,约 2000ms
    return value
  })

chain.catch(console.error)

如果在同一个源 Promise 上注册两个监听:

const source = new TinyPromise(resolve => {
  setTimeout(() => resolve(1), 1000)
})

source.then(value => {
  console.log('chain A:', value)
  return new TinyPromise(resolve => {
    setTimeout(() => resolve(value + 1), 1000)
  })
}).then(value => console.log('chain A result:', value))

source.then(value => {
  console.log('chain B:', value)
})

chain B 会在源 Promise 完成后很快执行,并不会等待 chain A 返回的第二个 Promise;它们是两条独立的链。

八、Promise/A+ 与 ECMAScript Promise 的关系

Promises/A+ 主要规定 then 的行为和 Promise Resolution Procedure,关注不同 Promise 实现之间的互操作;它并不完整规定构造器、catchfinallyallrace、微任务队列或浏览器/Node.js 宿主行为。

ECMAScript 原生 Promise 还规定:

  • Promise 构造器和 resolving functions;
  • thencatchfinally
  • resolverejectallallSettledanyracewithResolvers 等 API;
  • Promise jobs 的异步调度;
  • 与 async/await、模块和宿主 API 的集成。

因此,通过 Promise/A+ 测试并不等于实现了完整的原生 Promise;反过来,学习 thenable 解析仍然是理解链式调用的核心。

九、常见错误

9.1 只判断 instanceof

function isPromiseLike(value) {
  return value !== null &&
    (typeof value === 'object' || typeof value === 'function') &&
    typeof value.then === 'function'
}

读取 then 可能抛错或触发 getter,真实解析过程必须放进 try/catch,并且只读取一次。

9.2 用 return 返回错误对象

返回 Error 对象是 fulfilled,不是 rejected:

Promise.resolve()
  .then(() => new Error('普通返回值'))
  .then(value => console.log(value instanceof Error)) // true

Promise.resolve()
  .then(() => { throw new Error('抛出错误') })
  .catch(error => console.log(error.message)) // 抛出错误

9.3 把多个 then 当成同一条链

const source = Promise.resolve(1)
const first = source.then(value => value + 1)
const second = source.then(value => value + 2)

Promise.all([first, second]).then(console.log) // [2, 3]

要让后一个步骤等待前一个步骤,必须把它写在前一个 then 返回的 Promise 上:

Promise.resolve(1)
  .then(value => value + 1)
  .then(value => value + 2)
  .then(console.log) // 4

十、总结

  • then 每次返回新的 Promise,这是链式调用的根本;
  • 处理函数返回普通值、抛出异常或返回 thenable,会分别影响新 Promise 的状态;
  • 非函数处理器实现值穿透和错误继续传递;
  • 同一个 Promise 可以注册多个回调,回调按注册顺序执行,但每个 then 开启独立链;
  • 标准 Promise 的处理函数异步执行,即使源 Promise 已经 settled;
  • Promise 解析过程必须处理 thenable、一次性调用、异常和自引用;
  • instanceof MyPromise 不能代替 thenable 解析;
  • Promise/A+ 主要规定 then 互操作,不等于完整 ECMAScript Promise;
  • 手写实现适合学习,生产代码应使用原生 Promise 或经过验证的库。

参考资料

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS