还原一个完整符合规范的 Promise(二)

接着上一篇文章继续写:

本篇内容,让上篇的 MiniPromise 支持链式调用,并且符合 Promise/A+ 规范。这个规范有 872 个测试用例,实现目标是一个都不挂。

上篇的 MiniPromise 能跑异步、有 catch/finally 的 mini Promise,但它丢了一个 Promise 最核心的能力:链式调用

下面的就是链式调用示例:

js 复制代码
fetch('/api/user')
  .then((res) => res.json()) // 第一个 then 返回新 Promise
  .then((user) => fetch(`/api/orders/${user.id}`)) // 第二个 then
  .then((res) => res.json()) // 第三个 then
  .catch(console.error);

每一个 .then() 都返回一个全新的 Promise ,后一个 .then() 实际上是挂在这个新 Promise 上的。这就是链的精髓。

让上篇的 MiniPromise 支持链式调用,并且符合 Promise/A+ 规范。这个规范有 872 个测试用例,实现目标是一个都不挂。


then 必须返回一个新 Promise

1.1 什么是「新 Promise」

看这个代码:

js 复制代码
const p1 = Promise.resolve(1);
const p2 = p1.then((v) => v + 1);

console.log(p1 === p2); // false 不是同一个对象!

.then() 不会修改原来的 Promise,而是创建一个新的 Promise。新 Promise 的状态由回调的返回值决定。

1.2 动手实现

js 复制代码
then(onFulfilled, onRejected) {
  // 关键:then 返回一个新的 Promise 实例
  const promise2 = new MiniPromise((resolve, reject) => {
    if (this.state === FULFILLED) {
      // 执行成功回调,用返回值 resolve promise2
      const x = onFulfilled(this.value);
      resolve(x);
    }
    if (this.state === REJECTED) {
      const x = onRejected(this.reason);
      resolve(x); // 注意:错误被处理了,promise2 是 fulfilled
    }
    if (this.state === PENDING) {
      this.onFulfilledCallbacks.push(() => {
        const x = onFulfilled(this.value);
        resolve(x);
      });
      this.onRejectedCallbacks.push(() => {
        const x = onRejected(this.reason);
        resolve(x);
      });
    }
  });

  return promise2;
}

验证链式调用

js 复制代码
const p = new MiniPromise((resolve) => resolve(1));

p.then((v) => {
    console.log('第一个 then:', v); // 1
    return v + 1;
  })
  .then((v) => {
    console.log('第二个 then:', v); // 2
    return v + 1;
  })
  .then((v) => {
    console.log('第三个 then:', v); // 3
  });

能跑了, 但还有两个问题需要解决。


不传回调也能传递值--值穿透

2.1 原生 Promise 的神奇行为

js 复制代码
Promise.resolve(1)
  .then()        // 没传任何回调!
  .then()        // 还是没传
  .then((v) => console.log(v)); // 输出 1

这个特性叫值穿透 。如果一个 .then() 没传成功回调,值就自动传给下一个 .then()。错误也一样:如果没传失败回调,错误会沿着链一直穿透,直到遇到 .catch()

js 复制代码
Promise.reject('出错了')
  .then((v) => v)     // 有成功回调,但错误不会进这里
  .then((v) => v)     // 同上
  .catch((e) => console.log(e)); // '出错了'

2.2 实现值穿透

原理很简单:如果用户没传回调,我们就给一个默认回调

  • 成功默认回调:(v) => v ------ 把值直接返回
  • 失败默认回调:(e) => { throw e } ------ 把错误重新抛出去
js 复制代码
then(onFulfilled, onRejected) {
  // 值穿透处理
  onFulfilled = typeof onFulfilled === 'function'
    ? onFulfilled
    : (value) => value; // 默认:原样传递

  onRejected = typeof onRejected === 'function'
    ? onRejected
    : (err) => { throw err; }; // 默认:重新抛出错误

  const promise2 = new MiniPromise((resolve, reject) => {
    // 同时用 try/catch 包裹,防止回调抛异常
    if (this.state === FULFILLED) {
      try {
        const x = onFulfilled(this.value);
        resolve(x);
      } catch (e) {
        reject(e);
      }
    }
    if (this.state === REJECTED) {
      try {
        const x = onRejected(this.reason);
        resolve(x);
      } catch (e) {
        reject(e);
      }
    }
    if (this.state === PENDING) {
      this.onFulfilledCallbacks.push(() => {
        try {
          const x = onFulfilled(this.value);
          resolve(x);
        } catch (e) {
          reject(e);
        }
      });
      this.onRejectedCallbacks.push(() => {
        try {
          const x = onRejected(this.reason);
          resolve(x);
        } catch (e) {
          reject(e);
        }
      });
    }
  });

  return promise2;
}

验证

js 复制代码
const p = new MiniPromise((resolve) => resolve('hello'));

p.then()        // 没传回调
  .then()        // 没传回调
  .then((v) => console.log(v)); // 'hello' ✅

const p2 = new MiniPromise((resolve, reject) => reject('错误'));

p2.then((v) => v)   // 只传了成功回调
  .then((v) => v)    // 只传了成功回调
  .catch((e) => console.log(e)); // '错误' ✅ 穿透成功!

验证异常捕获

js 复制代码
new MiniPromise((resolve) => resolve('ok'))
  .then((v) => {
    throw new Error('中间炸了');
  })
  .then((v) => console.log('不会执行'))
  .catch((e) => console.log('捕获:', e.message)); // 捕获: 中间炸了

Promise 解析过程(resolvePromise)

3.1 问题出在哪?

先看这段代码:

js 复制代码
Promise.resolve(1)
  .then((v) => {
    // 回调返回了一个新的 Promise!
    return new Promise((resolve) => {
      setTimeout(() => resolve(v + 1), 1000);
    });
  })
  .then((v) => console.log(v));

这段代码的行为是什么?

  • 1 秒后打印 2
  • 第二个 .then() 在等待第一个 .then() 回调里返回的那个 Promise

但现在 MiniPromise 代码现在的逻辑如下:

js 复制代码
const x = onFulfilled(this.value);  // x 是一个 Promise 对象
resolve(x);  // 直接把 Promise 对象当成值传下去了  不对!

这样第二个 .then() 会立即拿到一个 Promise 对象,而不是等它 resolve 后的 2

3.2 规范怎么说的?

Promise/A+ 规范的第 2.3 节定义了 Promise Resolution Procedure,专门处理这个问题。核心思想是:

如果 x 是一个 thenable(有 .then 方法的对象或函数),就用它的 .then 方法来决定 promise2 的状态。

通俗一点来说就是:如果回调返回了一个 Promise,你得等这个 Promise 完成,然后把它的结果传递给下一个 then。

这个逻辑需要单独抽一个函数来处理,规范里叫 [[Resolve]](promise, x)

js 复制代码
/**
 * Promise 解析过程 ------ 核心中的核心
 *
 * @param {Promise} promise2 - then 返回的那个新 Promise
 * @param {*} x - 回调函数的返回值
 * @param {Function} resolve - promise2 的 resolve
 * @param {Function} reject - promise2 的 reject
 */
function resolvePromise(promise2, x, resolve, reject) {
  // 【规则 2.3.1】如果 promise2 和 x 是同一个东西,死循环了
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected'));
  }

  // 【防抖】确保 resolve/reject 只被调用一次
  let called = false;

  // 【规则 2.3.3】如果 x 是对象或函数
  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      // 拿 x.then,可能抛异常(比如访问受限属性)
      const then = x.then;

      // 【规则 2.3.3.3】如果 then 是函数,认为 x 是一个 thenable
      if (typeof then === 'function') {
        // 用 x 作为 this 调用 then 方法
        then.call(
          x,
          // 成功回调:resolvePromise
          (y) => {
            if (called) return;
            called = true;
            // 🌀 关键:递归解析!因为 y 可能还是一个 Promise
            resolvePromise(promise2, y, resolve, reject);
          },
          // 失败回调:rejectPromise
          (r) => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      } else {
        // 【规则 2.3.3.4】then 不是函数,x 是普通对象,直接 resolve
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    // 【规则 2.3.4】x 不是对象也不是函数(数字、字符串、null 等),直接 resolve
    resolve(x);
  }
}

3.3 尝试拆解一下这个函数

不要被代码量吓到,只看重点,拆开来看就是以下四个场景:

场景 1:x 是普通值(数字、字符串、null、undefined)

ini 复制代码
x = 42 → 直接 resolve(42),简单。

场景 2:x 是普通对象(没有 .then 方法)

css 复制代码
x = { name: '熊' } → 直接 resolve({ name: '熊' })

场景 3:x 是 Promise(有 .then 方法)

scss 复制代码
x = new Promise(resolve => setTimeout(() => resolve(42), 1000))
→ 调用 x.then(
    y => resolvePromise(promise2, y, resolve, reject),  // y 可能是 42
    r => reject(r)
  )
→ 1 秒后,y = 42,递归调用 resolvePromise(promise2, 42, resolve, reject)
→ 42 是普通值 → resolve(42) ✅

场景 4:x === promise2(循环引用)

scss 复制代码
p.then(() => p)  → 直接 reject(TypeError)

resolvePromise 的递归过程

假设有这样的代码:

js 复制代码
new MiniPromise((resolve) => resolve(1))
  .then((v) => new MiniPromise((resolve) => resolve(v + 1)))
  .then((v) => console.log(v));

resolvePromise 的执行流程:

ini 复制代码
第一次调用:
  promise2 = then1返回的Promise
  x = MiniPromise(resolve => resolve(2))
  
  x 是对象 ✓
  x.then 是函数 ✓
  → 调用 x.then(resolvePromise, reject)
  
  异步:x 的 resolve(2) 触发
  → resolvePromise 被调用,参数 y = 2
  
第二次调用(递归):
  promise2 = 同上
  x = 2
  
  x 是数字,不是对象 → resolve(2)
  → promise2 变成 fulfilled,值为 2
  → 第二个 .then(v => console.log(v)) 打印 2

3.4 把 resolvePromise 接到 then 里

js 复制代码
then(onFulfilled, onRejected) {
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v;
  onRejected = typeof onRejected === 'function' ? onRejected : (e) => { throw e; };

  const promise2 = new MiniPromise((resolve, reject) => {
    if (this.state === FULFILLED) {
      setTimeout(() => {
        try {
          const x = onFulfilled(this.value);
          // 🆕 用 resolvePromise 代替 resolve(x)
          resolvePromise(promise2, x, resolve, reject);
        } catch (e) {
          reject(e);
        }
      }, 0);
    }
    if (this.state === REJECTED) {
      setTimeout(() => {
        try {
          const x = onRejected(this.reason);
          resolvePromise(promise2, x, resolve, reject);
        } catch (e) {
          reject(e);
        }
      }, 0);
    }
    if (this.state === PENDING) {
      this.onFulfilledCallbacks.push(() => {
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      });
      this.onRejectedCallbacks.push(() => {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      });
    }
  });

  return promise2;
}

setTimeout(..., 0) 把回调包装成了异步, 是为了模拟 Promise 回调的微任务行为(真正的 Promise 用微任务,MiniPromise 暂时用宏任务近似)。

验证then 里返回 Promise

js 复制代码
new MiniPromise((resolve) => resolve(1))
  .then((v) => {
    console.log('then1:', v);
    return new MiniPromise((resolve) => {
      setTimeout(() => resolve(v + 1), 1000);
    });
  })
  .then((v) => {
    console.log('then2:', v); // 1 秒后打印 2 ✅
  });

验证循环引用检测

js 复制代码
const p = new MiniPromise((resolve) => resolve());
const p2 = p.then(() => p2); // 自己返回自己

p2.catch((e) => console.log(e.message));
// Chaining cycle detected ✅

🧪 高级测试:多层嵌套 Promise

js 复制代码
new MiniPromise((resolve) => resolve(1))
  .then((v) => {
    return new MiniPromise((resolve) => {
      resolve(
        new MiniPromise((resolve) => {
          resolve(
            new MiniPromise((resolve) => {
              resolve(v + 10);
            })
          );
        })
      );
    });
  })
  .then((v) => console.log(v)); // 11 ✅ 递归展开了 3 层!

用 promises-aplus-tests 验证

Promise/A+ 规范有一个官方的测试套件 promises-aplus-tests,包含 872 个测试用例。我们来跑一下。

4.1 写出完整代码

js 复制代码
// my-promise.js
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected'));
  }

  let called = false;

  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      const then = x.then;
      if (typeof then === 'function') {
        then.call(
          x,
          (y) => {
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject);
          },
          (r) => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      } else {
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    resolve(x);
  }
}

class MyPromise {
  constructor(executor) {
    this.state = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.state === PENDING) {
        this.state = FULFILLED;
        this.value = value;
        this.onFulfilledCallbacks.forEach((fn) => fn());
      }
    };

    const reject = (reason) => {
      if (this.state === PENDING) {
        this.state = REJECTED;
        this.reason = reason;
        this.onRejectedCallbacks.forEach((fn) => fn());
      }
    };

    try {
      executor(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v;
    onRejected = typeof onRejected === 'function' ? onRejected : (e) => { throw e; };

    const promise2 = new MyPromise((resolve, reject) => {
      const fulfilledTask = () => {
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      const rejectedTask = () => {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };

      if (this.state === FULFILLED) {
        fulfilledTask();
      } else if (this.state === REJECTED) {
        rejectedTask();
      } else if (this.state === PENDING) {
        this.onFulfilledCallbacks.push(fulfilledTask);
        this.onRejectedCallbacks.push(rejectedTask);
      }
    });

    return promise2;
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }
}

关于 catch 的三个重要认知

第一个:catch 就是 then(null, callback) 的语法糖。 但社区强烈推荐用 .catch() 而不是 .then(null, fn),原因很简单------.then(successFn, errorFn) 里的 errorFn 只能捕获 then 之前的错误,捕获不到 successFn 里的错误

js 复制代码
// 不好的写法:then 的第二个参数抓不到 success 回调里的错
promise.then(
  (data) => {
    throw new Error('处理数据时出错');
  },
  (err) => {
    // 抓不到上面那个错误!因为它只监听 promise 的 reject
    console.log('抓不到');
  }
);

// 好写法:catch 在后面,前面 then 里的错也能收
promise
  .then((data) => {
    throw new Error('处理数据时出错');
  })
  .catch((err) => {
    console.log('抓到了!', err.message);
  });

通俗一点就是:then 的成功回调和失败回调各管各的,但 .catch() 像一张大网,前面所有链上的错误都能兜住。

第二个:catch 之后状态变成 fulfilled,链可以继续走。 这叫「错误恢复」。

js 复制代码
Promise.reject('出错了')
  .catch((e) => {
    console.log('处理错误:', e);
    return '已恢复'; // ← catch 返回了值,Promise 状态变 fulfilled
  })
  .then((v) => {
    console.log('继续:', v); // 继续: 已恢复 ✅
  });

但 catch 里如果又 throw 了,那就是新的 rejection,需要后面再有 catch 接着。

js 复制代码
someAsyncThing()
  .then(fn)
  .catch((e) => {
    console.log('第一个错误:', e);
    y + 2; // y 没定义,又出错了!
  })
  .catch((e) => {
    console.log('第二个错误:', e); // 这个 catch 抓到了上面 catch 里的错
  });

第三个:没有 catch 的 Promise,错误不会被外部 try/catch 捕获。 Promise 会「吃掉」内部错误------代码不会崩,但错误消失了。Node.js 有 unhandledRejection 事件专门抓这种静默失败。建议生产环境全局注册它。

js 复制代码
// Node.js
process.on('unhandledRejection', (reason, promise) => {
  console.error('未捕获的 Promise 错误:', reason);
  // 上报到错误监控平台
});

// 浏览器
window.addEventListener('unhandledrejection', (event) => {
  console.error('未捕获的 Promise 错误:', event.reason);
});

第四步:用 promises-aplus-tests 验证

Promise/A+ 规范有一个官方的测试套件 promises-aplus-tests,包含 872 个测试用例。我们来跑一下。

4.1 写出完整代码

js 复制代码
// my-promise.js
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected'));
  }

  let called = false;

  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      const then = x.then;
      if (typeof then === 'function') {
        then.call(
          x,
          (y) => {
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject);
          },
          (r) => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      } else {
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    resolve(x);
  }
}

class MyPromise {
  constructor(executor) {
    this.state = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.state === PENDING) {
        this.state = FULFILLED;
        this.value = value;
        this.onFulfilledCallbacks.forEach((fn) => fn());
      }
    };

    const reject = (reason) => {
      if (this.state === PENDING) {
        this.state = REJECTED;
        this.reason = reason;
        this.onRejectedCallbacks.forEach((fn) => fn());
      }
    };

    try {
      executor(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v;
    onRejected = typeof onRejected === 'function' ? onRejected : (e) => { throw e; };

    const promise2 = new MyPromise((resolve, reject) => {
      const fulfilledTask = () => {
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      const rejectedTask = () => {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };

      if (this.state === FULFILLED) {
        fulfilledTask();
      } else if (this.state === REJECTED) {
        rejectedTask();
      } else if (this.state === PENDING) {
        this.onFulfilledCallbacks.push(fulfilledTask);
        this.onRejectedCallbacks.push(rejectedTask);
      }
    });

    return promise2;
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }
}

module.exports = MyPromise;

4.2 跑测试

上面输出的是完整版的代码,跑测试的话需要添加导出deferred测试适配器

javascript 复制代码
// 上方的 module.exports = MyPromise 改为:
module.exports = {
  deferred() {
    let resolve, reject;
    const promise = new MyPromise((res, rej) => {
      resolve = res;
      reject = rej;
    });
    return {
      promise,
      resolve,
      reject
    };
  }
};
bash 复制代码
npm install promises-aplus-tests --save-dev
npx promises-aplus-tests my-promise.js

!image-20260627132905402(/Users/jindacheng/Library/Application Support/typora-user-images/image-20260627132905402.png)

如果看到 872 passing,那么就说明通过了 Promise/A+ 规范的全部测试!


实践

练习 1:实现 delay 函数(链式调用版)

js 复制代码
function delay(ms, value) {
  return new MyPromise((resolve) => {
    setTimeout(() => resolve(value), ms);
  });
}

delay(500, '第一步')
  .then((v) => {
    console.log(v);
    return delay(500, '第二步');
  })
  .then((v) => {
    console.log(v);
    return delay(500, '第三步');
  })
  .then(console.log);

// 每 500ms 依次打印:第一步 → 第二步 → 第三步

练习 2:实现一个请求重试函数

js 复制代码
/**
 * 请求重试
 * @param {Function} fn - 返回 Promise 的请求函数
 * @param {number} times - 最大重试次数
 * @param {number} delayMs - 重试间隔
 */
function retry(fn, times = 3, delayMs = 1000) {
  return new MyPromise((resolve, reject) => {
    function attempt(n) {
      fn()
        .then(resolve)
        .catch((err) => {
          if (n <= 1) {
            return reject(err);
          }
          console.log(`第 ${times - n + 1} 次失败,${delayMs}ms 后重试...`);
          delay(delayMs).then(() => attempt(n - 1));
        });
    }
    attempt(times);
  });
}

// 模拟一个不稳定请求
let count = 0;
function unstableRequest() {
  return new MyPromise((resolve, reject) => {
    setTimeout(() => {
      count++;
      if (count < 3) {
        reject(new Error('网络错误'));
      } else {
        resolve('成功!');
      }
    }, 500);
  });
}

retry(unstableRequest, 5, 1000).then(console.log);
// 第 1 次失败,1000ms 后重试...
// 第 2 次失败,1000ms 后重试...
// 成功!

练习 3:实现 Promise 顺序执行

js 复制代码
/**
 * 顺序执行 Promise 数组
 */
function sequence(tasks) {
  // 用 reduce 构建一个 Promise 链
  return tasks.reduce((prev, task) => {
    return prev.then((results) => {
      return task().then((result) => {
        results.push(result);
        return results;
      });
    });
  }, MyPromise.resolve([]));
}

// 模拟三个网络请求
const tasks = [
  () => delay(1000, '用户数据'),
  () => delay(500, '订单数据'),
  () => delay(800, '商品数据'),
];

sequence(tasks).then((results) => {
  console.log('全部完成:', results);
  // 全部完成: ['用户数据', '订单数据', '商品数据']
  // 总耗时约 2300ms(串行)
  
  // 但是输出 TypeError: MyPromise.resolve is not a function 
  // 这个resolve静态方法没有实现,下篇文章实现,实现了之后回过头来可以解决
});

这篇完成了一个符合 Promise/A+ 规范的完整 Promise,核心两点:

  1. then 返回新 Promise ------ 这是链式调用的基础
  2. resolvePromise 解析过程 ------ 处理 thenable 递归、循环引用、多次调用等边界

现在写出来的 MyPromise 已经可以通过 872 个规范测试。但还有一些问题没解决,比如:

  • setTimeout 是宏任务,真正的 Promise 回调是微任务,时序上有差异
  • 还没有实现 Promise.resolve/Promise.reject/Promise.all/Promise.race 等静态方法
  • 还不知道 async/await 到底是怎么实现的

这些,留到下篇继续写。


面试题

第 1 题:链式调用的 return 值有什么讲究?

js 复制代码
Promise.resolve(1)
  .then((v) => {
    console.log('then1:', v);
    return v + 1;
  })
  .then((v) => {
    console.log('then2:', v);
    // 这行忘了写 return
  })
  .then((v) => {
    console.log('then3:', v);
  });

输出什么?

makefile 复制代码
then1: 1
then2: 2
then3: undefined

.then() 没有 return 时,默认返回 undefined。下一个 .then 拿到的是 undefined,不是上一个的值。链式调用要传递值,每一步都得 return。


第 2 题:then 里 return Promise 会怎样?

js 复制代码
Promise.resolve(1)
  .then((v) => {
    return new Promise((resolve) => {
      setTimeout(() => resolve(v + 10), 1000);
    });
  })
  .then((v) => {
    console.log(v);
  });

输出?为什么是 11 而不是 Promise 对象?

1 秒后输出:11

.then 的回调返回一个 Promise 时,后面的 .then 不会拿到这个 Promise 对象,而是等待这个 Promise resolve ,拿到 resolve 的值。这就是 resolvePromise 干了三件事中的核心:递归展开 thenable


第 3 题:then 里 return 同一个 thenable 会怎样?

js 复制代码
const p = new MyPromise((resolve) => resolve(1));
const p2 = p.then(() => p2);
p2.catch((e) => console.log(e.message));

用完整版 MyPromise 跑这段代码会怎样?

输出:Chaining cycle detected

p2.then(() => p2) 让 promise2 尝试等待自己 resolve------这是一个循环引用。Promise/A+ 规范规定 x === promise2 时直接 reject(TypeError),我们在 resolvePromise 里加了这层检查。


第 4 题:错误冒泡------哪个 catch 会收到?

js 复制代码
Promise.resolve(1)
  .then((v) => {
    console.log('A:', v);
    throw new Error('爆炸');
  })
  .then((v) => {
    console.log('B:', v);
  })
  .catch((e) => {
    console.log('catch:', e.message);
  })
  .then((v) => {
    console.log('C:', v);
  });

输出什么?

makefile 复制代码
A: 1
catch: 爆炸
C: undefined

第一个 .then 抛错后,第二个 .then(B)被跳过,错误一路冒泡到最近的 .catch。而 .catch 本身也返回一个 Promise,它吃掉了错误后 resolve(undefined),所以 C 继续打印 undefined


第 5 题:手写题------请实现 Promise 的链式调用核心

用简单的方式解释:new Promise(...).then(...).then(...) 为什么可以一直 .then 下去?用一段伪代码说明思路。

js 复制代码
// 核心思路:then 每次返回新的 Promise
then(onFulfilled, onRejected) {
  const promise2 = new Promise((resolve, reject) => {
    // 当前状态 settled 时,用 setTimeout 异步执行回调
    if (this.state === FULFILLED) {
      setTimeout(() => {
        const x = onFulfilled(this.value);
        resolvePromise(promise2, x, resolve, reject);
      });
    }
    // pending 时把回调存队列
    if (this.state === PENDING) {
      this.onFulfilledCallbacks.push(() => { ... });
    }
  });
  return promise2; // ← 关键:返回新 Promise,链才能继续
}

三个要点:

  1. then 每次返回新的 Promise(不是 this)
  2. 回调执行结果 x 通过 resolvePromise 展开
  3. resolvePromise 处理三种情况:普通值、thenable、循环引用

欢迎关注公众号:程序员蜡笔熊,期待与您的讨论

相关推荐
时光足迹1 小时前
腾讯云 TRTC UniApp SDK 从入门到上线
前端·vue.js·uni-app
时光足迹2 小时前
uni-app 里把加密视频嵌入页面播放?我分析了 4 种方案,只有 1 种接近完美
前端·vue.js·uni-app
To_OC2 小时前
万字解析《JS 语言精粹》之第五章:继承 5 大核心精髓(JS 原型核心)
前端·javascript·代码规范
时光足迹2 小时前
极光推送全攻略(上):被iOS证书折磨了三天,我写了一份前端也能看懂的避坑指南
前端·ios·uni-app
DyLatte2 小时前
AI 时代,最危险的不是被替代,而是努力不沉淀
前端·后端·程序员
mCell3 小时前
【锐评】桌面端技术营销:别拿跑分当工程判断
前端·rust·electron
柒和远方3 小时前
从一次工程审查看 AI 学习产品的边界兜底:RAG 资料链路一致性实战
前端·后端·架构
疯狂的魔鬼3 小时前
一个"懂分寸"的文本省略组件是怎样炼成的
前端·vue.js·设计
angerdream3 小时前
手把手编写儿童手机远程监控App之vue3 AI Gent
前端