JS炼化:promise你真的认识吗?别被网上那点题困住了

0 阅读8分钟

不背概念,从零开始设计promise

网上promise面试题漫天飞,手写源码一大堆,动辄几十上百行堆在眼前,看着就头大。很多人两眼一闭就是干,硬着头皮往前冲,结果学到最后直发懵,只会背语法、套模板,根本没搞懂它到底是怎么来的,更没法学以致用。

为什么写这一篇文章?

可能有人会说:学知识会写代码就行了呗,干嘛还要自己设计一遍,多此一举?

我的理由很简单:学习一个东西,就要从最根本的原因入手,理解它为什么出现、以什么形态诞生、如何被赋予方法、最后又该如何使用。虽然我下面这个抽象听起来有点中二,但道理就像学剑一样——不只是练会剑法招式,更要知道为何而起剑。只有懂了它的诞生逻辑,你才算真正掌握它。我称之为 “剑心”

所以:这篇内容不做科普,不教零碎知识点,也不堆砌 API,只干一件事:从零开始,完整设计一遍 Promise。  

废话不多说,设计正式开始

第一步:你发现致命问题,萌生设计想法

做前端开发久了,你敏锐发现:异步操作在工程里用得太多了,网络请求、定时器、文件读写全是异步,可当下只能用回调函数处理。单步异步凑活能用,可一旦遇到连续依赖的异步业务,代码直接嵌套成“回调地狱”,一层套一层无限右移,错误处理还得每层单独写,又乱又难维护,后期改代码简直是灾难,完全没法满足规范化开发的需求。

你大胆的想做出设计

既然这个问题这么致命,你决定自己造一个专门处理异步的工具,目标很明确:彻底干掉回调嵌套,让异步代码变得规整、好读、易维护,你把这个即将诞生的工具,提前命名为Promise

你看到的糟糕现状(回调地狱)

// 你发现的问题:多层回调嵌套,混乱不堪
// 业务:取用户ID->取用户信息->取订单,层层依赖
getUserId(function (userId) {
  console.log("用户ID:", userId);
  getUserInfo(userId, function (userInfo) {
    console.log("用户信息:", userInfo); // 注意:这里修正为 userInfo
    getOrderList(userInfo.name, function (orderList) {
      console.log("订单列表:", orderList);
    }, orderErr => console.log("订单失败", orderErr));
  }, infoErr => console.log("信息失败", infoErr));
}, idErr => console.log("ID失败", idErr));

 

第二步:你贴合JS主流,设计Promise核心结构

要造工具,不能脱离JS的核心逻辑,你清楚知道:JS的绝对主流编程方式是面向对象,所有规范工具都是“类+实例”的用法,要是自己设计的工具另搞一套,开发者用着别扭,也没法融入现有开发体系。

你提出了设计方案

1. 遵循面向对象逻辑,把Promise设计成JS内置类,不用开发者自己手写class,直接内置,拿来就用,更是省时省力;

2. 用 new Promise() 创建实例,一个实例对应一个异步任务,完全贴合“类→实例”的使用习惯;

3. 给Promise设计三种状态:pending(初始待定)、fulfilled(成功)、rejected(失败),状态一旦改变就不可逆,保证异步结果唯一,避免数据混乱;

4. 实例创建时自动执行执行器函数,用来包裹异步代码,同时内置 resolve 和 reject 两个方法,分别控制成功、失败的状态切换,还能传递结果和错误。

你亲手设计的Promise基础结构

// 你设计的Promise基础结构,贴合面向对象
// new创建实例,执行器包裹异步,resolve/reject控制状态
const yourPromise = new Promise((resolve, reject) => {
  // 你把异步操作放在这里
  setTimeout(() => {
    const result = { id: 1, name: "前端开发者" };
    const isSuccess = true;
    if (isSuccess) {
      // 你设计的成功触发方法
      resolve(result);
    } else {
      // 你设计的失败触发方法
      reject("请求失败");
    }
  }, 1000);
});

 

第三步:你设计then方法,彻底解决回调嵌套

基础结构有了,但你发现:光有状态控制,还是没法解决嵌套问题,异步结果拿出来后,还是容易写进回调里,必须再设计一个方法,把嵌套的代码“拉直”。

你的优化设计方案

专门为Promise实例,设计一个原型方法——then,这是你解决回调地狱的核心手段:

1. then方法接收成功回调,异步成功后自动执行,拿到resolve传递的结果;

2. 支持链式调用,then里return新的Promise,就能接着调用下一个then,彻底告别横向嵌套,让代码纵向扁平化;

3. 完全贴合面向对象的写法,实例直接调用,简单易懂。

你设计的then方法落地效果

// 封装异步方法:模拟获取用户ID的接口请求
// 返回Promise实例,用于后续链式调用
function getUserId() {
  // new Promise创建异步任务实例,遵循面向对象"类→实例"的设计逻辑
  return new Promise((resolve) => {
    // 用setTimeout模拟网络请求的异步延时
    setTimeout(() => {
      // 异步请求成功,调用resolve方法,将Promise状态改为fulfilled
      // 并传递成功结果:用户ID=1
      resolve(1);
    }, 500); // 模拟500ms的接口响应时间
  });
}

// 封装异步方法:模拟根据用户ID获取用户信息的接口请求
// 接收参数userId:上一步获取到的用户ID,实现异步依赖
// 返回Promise实例,用于链式调用
function getUserInfo(userId) {
  return new Promise((resolve) => {
    setTimeout(() => {
      // 异步请求成功,调用resolve,传递用户信息对象
      resolve({ id: userId, name: "前端开发者" });
    }, 500);
  });
}

// 你设计的then链式调用:彻底解决回调地狱,实现代码扁平化
// 从getUserId()发起异步,通过then依次处理连续依赖的异步任务
getUserId()
  // 第一个then:接收getUserId() resolve的userId,处理第一步异步结果
  .then((userId) => {
    console.log("第一步:获取用户ID", userId);
    // return新的Promise实例,实现链式传递,为下一个then做准备
    return getUserInfo(userId);
  })
  // 第二个then:接收getUserInfo() resolve的userInfo,处理第二步异步结果
  .then((userInfo) => {
    console.log("第二步:获取用户信息", userInfo);
  });

  这一步设计完,回调地狱彻底被解决,异步代码变得无比规整,这也是Promise最核心的价值,你做到了

第四步:你完善配套方法,让Promise更全能

then方法的确解决了核心嵌套问题,但你又发现:错误处理不够统一,收尾逻辑没地方写,复杂异步(多个异步并行)也没法处理,这套工具还不够完善。

你提出的完善方案

在核心设计的基础上,继续补充配套方法,让Promise覆盖所有异步场景:

1. 设计catch方法:专门捕获整个异步链的所有错误,不管是reject抛出的,还是代码报错的,一处捕获,全部处理,不用每层写错误回调;

2. 设计finally方法:无论异步成功还是失败,最终都会执行,用来处理加载关闭、定时器清除等收尾逻辑;

3. 设计静态方法:Promise.resolve()/reject()快速创建成功/失败Promise,Promise.all()/race()处理多个异步并行、竞争场景。

你完善后的完整Promise方案

// 你补充订单接口封装
function getOrderList(userName) {
  // 封装异步请求,返回Promise实例,模拟500ms接口延时
  return new Promise(resolve => 
    setTimeout(() => resolve([{ orderId: 101 }]), 500)
  );
}

// 你最终设计的完整Promise用法
getUserId()
  // 第一步:获取用户ID
  .then(userId => getUserInfo(userId))
  // 第二步:根据ID获取用户信息
  .then(userInfo => getOrderList(userInfo.name))
  // 第三步:根据用户名获取订单列表并打印
  .then(orderList => console.log("订单:", orderList))
  // 你设计的catch:统一错误捕获,任何环节出错都会触发
  .catch(err => console.log("全流程错误:", err))
  // 你设计的finally:统一收尾,无论成功失败都会执行
  .finally(() => console.log("异步流程结束"));

最终:你完成整套设计,正式将其命名为Promise

经过你发现问题→贴合范式设计核心→设计then解决嵌套→完善配套方法这四步,一套完整、规范、好用的异步处理工具彻底成型,你正式把它命名为Promise

它不是凭空出现的语法,而是你一步步针对实际问题,顺着JS面向对象的主流逻辑,亲手设计出来的解决方案。所有的状态、resolve/reject、then/catch/finally,全都是你为了解决对应问题,专门设计的,没有一个多余的知识点。

结尾

学Promise从来不是死背API、硬抄源码,而是要从问题出发,顺着它的诞生逻辑去理解:从为解决回调地狱而生,到贴合JS面向对象设计核心结构,再用then拉直嵌套、用catch/finally补全场景,每一个语法、每一个方法都是为解决实际问题量身打造的。

当你摸清了这些底层设计逻辑,就再也不会对它发怵,学习不再是零散知识点的堆砌,而是完整的知识补全,心里有了底气。后续再顺着这套思路去补充细节、练习实战,就能真正把 Promise 吃透,也能更稳地掌握 JS 异步编程。呼应开头,这便是读懂 “剑心”,招式自然随心所用。

学习分享,如有理解不当或者出错,欢迎大家指正一起学习进步~