【JavaScript】Promise对象使用方式研究和理解

这里为爱计算机编程的小学生整理的完整学习文章,在原代码里加入了大量比喻、图解思路(用文字描述)和分步讲解,让小朋友也能像读故事一样学懂 Promise


【JavaScript】Promise 对象使用方式研究和理解

适合读者 :刚开始学 JavaScript、对异步操作感到好奇的小学生朋友们
学习目标:理解 Promise 是做什么的,能看懂代码,还能自己动手写一个简化版


文章目录

  • [【JavaScript】Promise 对象使用方式研究和理解](#【JavaScript】Promise 对象使用方式研究和理解)
    • [一、Promise 是什么?一个生活中的例子](#一、Promise 是什么?一个生活中的例子)
    • [二、Promise 的基本用法](#二、Promise 的基本用法)
    • 三、原代码的问题分析
      • [问题 1:类里不能用 `var`](#问题 1:类里不能用 var)
      • [问题 2:`then` 没有返回新的 Promise](#问题 2:then 没有返回新的 Promise)
      • [问题 3:异步支持不够](#问题 3:异步支持不够)
      • [问题 4:状态值用数字不够直观](#问题 4:状态值用数字不够直观)
    • [四、手把手实现 MyPromise](#四、手把手实现 MyPromise)
    • 五、静态方法小课堂
      • [1. `Promise.resolve()` ------ 立刻成功的 Promise](#1. Promise.resolve() —— 立刻成功的 Promise)
      • [2. `Promise.reject()` ------ 立刻失败的 Promise](#2. Promise.reject() —— 立刻失败的 Promise)
      • [3. `Promise.all()` ------ 等待所有任务完成](#3. Promise.all() —— 等待所有任务完成)
    • [六、完整版 MyPromise 代码(可直接复制运行)](#六、完整版 MyPromise 代码(可直接复制运行))
    • 七、给小朋友的记忆口诀
    • 八、课后小挑战

一、Promise 是什么?一个生活中的例子

想象你让妈妈帮你买一个玩具

  1. 妈妈说:"好的,我答应你,明天去买。"
  2. 这时候你不知道能不能买到,只能等待
  3. 第二天有两种结果:
    • 买到了(成功)→ 你很开心,拿着玩具去玩。
    • 没买到(失败)→ 你有点难过,妈妈解释了原因。

在 JavaScript 里,Promise(承诺)就像妈妈说的那句"我答应你"。它代表一个现在还没完成,但未来会有结果的事情。

Promise 有三种状态,就像红绿灯

状态 英文 意思 比喻
🟡 黄灯 pending 等待中 妈妈正在去买玩具的路上
🟢 绿灯 fulfilled 已成功 玩具买到啦!
🔴 红灯 rejected 已失败 商店关门了,没买到

重要规则 :一旦从黄灯变成绿灯或红灯,就不能再变回去了!就像玩具已经买到了,不可能突然变成没买到。


二、Promise 的基本用法

在真正动手写之前,先看看真正的 Promise 是怎么用的:

javascript 复制代码
// 创建一个新的 Promise
const myPromise = new Promise((resolve, reject) => {
  // 这里放异步操作,比如定时器、网络请求
  setTimeout(() => {
    const success = true; // 假设任务成功了
    
    if (success) {
      resolve("玩具买到啦!");  // 🟢 绿灯:成功,把结果传出去
    } else {
      reject("商店关门了");     // 🔴 红灯:失败,把原因传出去
    }
  }, 1000);
});

// 使用 Promise
myPromise
  .then((result) => {
    console.log("成功:", result);  // 收到 "玩具买到啦!"
  })
  .catch((error) => {
    console.log("失败:", error);   // 如果 reject,会进这里
  })
  .finally(() => {
    console.log("不管成功失败,我都会执行!"); // 类似"最后打扫战场"
  });

三个关键方法

方法 作用 什么时候执行
then() 处理成功 Promise 变成 fulfilled
catch() 处理失败 Promise 变成 rejected
finally() 最后收尾 不管成功失败都会执行

三、原代码的问题分析

你之前写的 MyPromise 非常有想法!但里面有几个"小陷阱",我们先把它们找出来,这样才能进步:

问题 1:类里不能用 var

javascript 复制代码
// ❌ 错误
class MyPromise {
  var state = 0;  // JavaScript 类里面不能这样写!
}
// ✅ 正确
class MyPromise {
  constructor() {
    this.state = 0;  // 要用 this 来存放每个对象自己的数据
  }
}

问题 2:then 没有返回新的 Promise

真正的 Promise 可以链式调用.then().then().catch()),因为每个 then 都会返回一个新的 Promise。原代码缺少这个。

问题 3:异步支持不够

如果 resolve 是在 setTimeout 里调用的,原代码可能收不到通知。

问题 4:状态值用数字不够直观

0, 1, 2, 3 容易搞混,我们用字符串 'pending', 'fulfilled', 'rejected' 更清晰。


四、手把手实现 MyPromise

我们分三步来实现,就像搭积木一样,从简单到完整。

第一步:最简骨架(能跑起来)

先做一个"能工作"的最小版本,理解核心逻辑:

javascript 复制代码
class MyPromise {
  constructor(executor) {
    // 初始化状态和数据
    this.state = 'pending';      // 一开始是黄灯:等待中
    this.value = undefined;      // 成功时带的数据
    this.reason = undefined;     // 失败时带的原因

    // 定义 resolve 函数:任务成功时调用
    const resolve = (value) => {
      if (this.state === 'pending') {  // 只有黄灯才能变绿灯
        this.state = 'fulfilled';
        this.value = value;
      }
    };

    // 定义 reject 函数:任务失败时调用
    const reject = (reason) => {
      if (this.state === 'pending') {  // 只有黄灯才能变红灯
        this.state = 'rejected';
        this.reason = reason;
      }
    };

    // 立刻执行用户传入的函数
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);  // 如果执行时出错,直接变红灯
    }
  }

  // 注册成功回调
  then(onFulfilled) {
    if (this.state === 'fulfilled') {
      onFulfilled(this.value);
    }
  }

  // 注册失败回调
  catch(onRejected) {
    if (this.state === 'rejected') {
      onRejected(this.reason);
    }
  }
}

// ========== 测试一下 ==========
const p = new MyPromise((resolve, reject) => {
  resolve("Hello, Promise!");
});

p.then((msg) => {
  console.log(msg);  // 输出:Hello, Promise!
});

小朋友思考题 :为什么 resolvereject 里面都要判断 this.state === 'pending'
答案 :防止有人不小心调用了两次 resolve,Promise 的状态只能改变一次!


第二步:支持异步操作(解决"妈妈还没回来"的问题)

上面的代码有个大问题:如果 resolve 是在 1 秒后才执行的,而 then 已经提前注册好了,就会错过通知

就像妈妈还没买回玩具,你就一直问"买到了吗",结果妈妈回来时你已经去睡觉了,没听到消息。

解决办法:准备一个"留言本"(回调队列)

javascript 复制代码
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(value));
      }
    };

    const reject = (reason) => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn => fn(reason));
      }
    };

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

  then(onFulfilled, onRejected) {
    if (this.state === 'fulfilled') {
      onFulfilled(this.value);
    } else if (this.state === 'rejected') {
      onRejected(this.reason);
    } else {
      // 还是黄灯状态?先把回调存到留言本里
      this.onFulfilledCallbacks.push(onFulfilled);
      this.onRejectedCallbacks.push(onRejected);
    }
  }

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

// ========== 测试异步 ==========
const p2 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve("异步任务完成!");
  }, 1000);
});

p2.then((msg) => {
  console.log(msg);  // 1秒后输出:异步任务完成!
});

形象理解

  • pending 时:then 把函数写进"留言本",不立刻执行。
  • fulfilled 时:按留言本顺序,一个个打电话通知。

第三步:支持链式调用(真正的 Promise 魔法)

真正的 Promise 最酷的地方是可以链式调用

javascript 复制代码
fetchData()
  .then(data => process(data))
  .then(result => save(result))
  .catch(err => console.error(err));

要实现链式调用,then 必须返回一个新的 Promise

javascript 复制代码
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 (err) {
      reject(err);
    }
  }

  then(onFulfilled, onRejected) {
    // 返回新的 Promise,才能继续 .then()
    return new MyPromise((resolve, reject) => {
      
      const handleFulfilled = () => {
        try {
          // 执行用户的回调,拿到返回值
          const result = onFulfilled ? onFulfilled(this.value) : this.value;
          resolve(result);  // 把返回值传给下一个 Promise
        } catch (err) {
          reject(err);  // 如果回调里报错,下一个 Promise 失败
        }
      };

      const handleRejected = () => {
        try {
          const result = onRejected ? onRejected(this.reason) : this.reason;
          resolve(result);
        } catch (err) {
          reject(err);
        }
      };

      if (this.state === 'fulfilled') {
        handleFulfilled();
      } else if (this.state === 'rejected') {
        handleRejected();
      } else {
        //  pending 状态,先存起来
        this.onFulfilledCallbacks.push(handleFulfilled);
        this.onRejectedCallbacks.push(handleRejected);
      }
    });
  }

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

  finally(onFinally) {
    return this.then(
      (value) => {
        onFinally();      // 成功时执行 finally
        return value;     // 把值继续传下去
      },
      (reason) => {
        onFinally();      // 失败时执行 finally
        throw reason;     // 把错误继续抛下去
      }
    );
  }
}

// ========== 链式调用测试 ==========
const p3 = new MyPromise((resolve, reject) => {
  setTimeout(() => resolve(10), 500);
});

p3
  .then(num => {
    console.log("第一步:", num);      // 10
    return num * 2;                   // 返回 20
  })
  .then(num => {
    console.log("第二步:", num);      // 20
    return num + 5;                   // 返回 25
  })
  .then(num => {
    console.log("第三步:", num);      // 25
  })
  .finally(() => {
    console.log("全部完成!");
  });

链式调用的秘密

每个 .then() 都是一座"小桥",桥的这头接收上一个 Promise 的结果,那头把新结果传给下一个 Promise。只要每座桥都返回新的 MyPromise,就能一直连下去!


五、静态方法小课堂

真正的 Promise 还有一些"快捷方式",我们也来简单实现一下:

1. Promise.resolve() ------ 立刻成功的 Promise

javascript 复制代码
MyPromise.resolve = function(value) {
  return new MyPromise((resolve) => resolve(value));
};

// 用法
MyPromise.resolve("直接成功").then(msg => console.log(msg));

2. Promise.reject() ------ 立刻失败的 Promise

javascript 复制代码
MyPromise.reject = function(reason) {
  return new MyPromise((resolve, reject) => reject(reason));
};

3. Promise.all() ------ 等待所有任务完成

就像你和三个朋友比赛做作业,要等所有人都做完才能一起去玩。

javascript 复制代码
MyPromise.all = function(promises) {
  return new MyPromise((resolve, reject) => {
    const results = [];
    let count = 0;
    
    promises.forEach((p, index) => {
      p.then(value => {
        results[index] = value;  // 按顺序保存结果
        count++;
        if (count === promises.length) {
          resolve(results);  // 全部完成!
        }
      }).catch(err => reject(err));  // 有一个失败就全失败
    });
  });
};

// 测试
const pA = MyPromise.resolve("苹果");
const pB = new MyPromise(r => setTimeout(() => r("香蕉"), 100));
const pC = MyPromise.resolve("樱桃");

MyPromise.all([pA, pB, pC]).then(fruits => {
  console.log(fruits);  // ["苹果", "香蕉", "樱桃"]
});

六、完整版 MyPromise 代码(可直接复制运行)

把上面学的全部拼起来,就是一个相对完整的版本啦:

javascript 复制代码
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 (err) {
      reject(err);
    }
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      const handleFulfilled = () => {
        try {
          const result = onFulfilled ? onFulfilled(this.value) : this.value;
          resolve(result);
        } catch (err) {
          reject(err);
        }
      };

      const handleRejected = () => {
        try {
          const result = onRejected ? onRejected(this.reason) : this.reason;
          resolve(result);
        } catch (err) {
          reject(err);
        }
      };

      if (this.state === 'fulfilled') {
        handleFulfilled();
      } else if (this.state === 'rejected') {
        handleRejected();
      } else {
        this.onFulfilledCallbacks.push(handleFulfilled);
        this.onRejectedCallbacks.push(handleRejected);
      }
    });
  }

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

  finally(onFinally) {
    return this.then(
      value => { onFinally(); return value; },
      reason => { onFinally(); throw reason; }
    );
  }

  // 静态方法
  static resolve(value) {
    return new MyPromise(r => r(value));
  }

  static reject(reason) {
    return new MyPromise((_, r) => r(reason));
  }

  static all(promises) {
    return new MyPromise((resolve, reject) => {
      const results = [];
      let count = 0;
      promises.forEach((p, i) => {
        p.then(v => {
          results[i] = v;
          count++;
          if (count === promises.length) resolve(results);
        }).catch(reject);
      });
    });
  }
}

七、给小朋友的记忆口诀

复制代码
Promise 像承诺,三种状态要记清:
pending 黄灯等一等,fulfilled 绿灯行,
rejected 红灯停一停。

then 成功 catch 失败,finally 收尾不管成败。
链式调用像搭桥,返回新 Promise 才能跑。
异步不用怕,留言本把回调存下。

八、课后小挑战

  1. 改一改 :试着给 MyPromise 添加一个 MyPromise.race() 方法,实现"多个 Promise 赛跑,谁第一个完成就用谁的结果"。
  2. 想一想 :如果 then 的回调里返回的是另一个 MyPromise,会发生什么?(提示:这叫"Promise 穿透")
  3. 做一做 :用 MyPromise 模拟一个"订外卖"的场景:下单 → 商家接单(1秒)→ 骑手配送(2秒)→ 送达,每一步都用 .then() 连接。

学习寄语:Promise 看起来有点绕,但其实就像生活中的"等一等,有结果了我告诉你"。多写几遍,多改几个例子,你就会发现它其实很讲道理。加油,小程序员!🚀

相关推荐
ZC跨境爬虫2 小时前
跟着 MDN 学 HTML day_36:(深入理解 Comment 接口与 DOM 注释节点)
前端·javascript·ui·html·音视频·视频编解码
白鳯2 小时前
塔罗神谕:星月神域莱诺薇为您占卜
react·web·three.js·codex·deepseek·vibe coding·塔罗占卜
栉甜2 小时前
Js进阶(4)
开发语言·javascript·原型模式
村上小树4 小时前
非常简单地学习一下shareDB的原理
前端·javascript
Hilaku4 小时前
求求你们🙏 ,别再换打包工具了?
前端·javascript·程序员
用户新4 小时前
V8引擎 精品漫游指南--Ignition篇(下 二) JavaScript 栈帧详解
前端·javascript
得闲喝茶4 小时前
JavaScript在数据处理的应用
开发语言·前端·javascript·经验分享·笔记
费曼学习法5 小时前
React Hooks 源码级揭秘:为什么必须按顺序调用?
javascript·react.js
之歆5 小时前
DAY_20JavaScript 条件语句与循环结构深度学习(二)
前端·javascript