Promise 学习笔记 | 从小白到入门

15 阅读4分钟

Promise 学习笔记 | 从小白到入门

这是一篇学习笔记,记录了我从回调地狱到 Promise 的学习过程。


一、先搞懂异步

JS 默认是单线程执行的。

啥意思?就是 JS 一次只能干一件事。

那遇到耗时的事咋办?比如向后端请求数据,要等 1 秒——

let a = 1;

setTimeout(() => {
  a = 2;
}, 1000);

console.log(a); // 输出多少?

答案是 1,不是 2

因为 setTimeout 是个异步任务,JS 碰到它,不会傻等着,而是先挂起来,继续往下执行。1 秒后回调才执行,把 a 改成 2

同步:按顺序往下走 异步:碰到耗时任务,先挂起,继续干别的,等耗时任务完了再回来处理


二、回调地狱——异步的痛

看这个场景:

let a = null;

function A() {
  // 向后端请求数据,耗时 1s
  setTimeout(() => {
    a = 100;
  }, 1000);
}

function B() {
  // 把拿到的数据渲染到页面上
  console.log(a);
}

A();
B(); // 输出 null,因为 A 还没执行完呢

明明先调了 A,但 B 拿不到 A 的结果。因为 A 是异步的。

咋解决?回调——把 B 放到 A 里面:

function A(callback) {
  setTimeout(() => {
    a = 100;
    callback(); // A 执行完了再调 B
  }, 1000);
}

A(B);

这样能解决问题。但一旦逻辑复杂了,回调套回调,就成了 回调地狱

getUser(function(user) {
  getOrders(user.id, function(orders) {
    getLogistics(orders[0].id, function(logistics) {
      console.log(logistics);
    });
  });
});

三层还好,十层八层的根本没法维护。


三、Promise 是什么

Promise 就是来解决回调地狱的。

官方说法:Promise 是异步编程的一种解决方案。

我的理解:Promise 就像一个中间人

举个栗子🌰:

小明想追女神(这就是一个异步任务,不知道结果),他找了红娘。

红娘给了他一个承诺(Promise):

  • ✅ 女神同意了 → 红娘通知小明(成功)
  • ❌ 女神拒绝了 → 红娘通知小明(失败)

小明不用天天干等着,该干嘛干嘛,有了结果红娘会通知他。


四、基本用法

function 追女生() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("felix love her");
      resolve();  // 成功了!
    }, 2000);
  });
}

new Promise 创建一个 Promise 对象,它接受一个函数,这个函数有两个参数:

  • resolve:成功了调它
  • reject:失败了调它

Promise 内部有个状态 state,一开始是 pending(等待中):

  • 调了 resolve → 变成 resolved(成功)
  • 调了 reject → 变成 rejected(失败)
  • 状态一旦变了,就不能再改

五、then() 和 catch()

怎么拿到 Promise 的结果?

追女生()
  .then(() => {
    console.log("在一起了!");
  })
  .catch(() => {
    console.log("被拒绝了...");
  });
  • .then() → 成功了执行
  • .catch() → 失败了执行

六、链式调用——告别回调地狱

Promise 最牛的地方:.then() 返回的也是一个 Promise,所以可以一直点下去。

看我的笔记里写的——追爱三部曲

function xq() {
  // 追爱,耗时 2s
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("felix love her");
      resolve();
    }, 2000);
  });
}

function marry() {
  // 结婚,耗时 1s
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("married successfully");
      resolve();
    }, 1000);
  });
}

function baby() {
  console.log("baby is born");
}

// 链式调用:追到→结婚→生娃
xq()
  .then(() => {
    return marry();
  })
  .then(() => {
    return baby();
  })
  .catch(() => {
    console.log("pity"); // 中间任何一步失败,都跳到这里
  });

对比回调地狱,Promise 的写法:

  • ✅ 扁平化了,从上往下读
  • ✅ 错误统一在 .catch() 处理
  • ✅ 加步骤直接加 .then() 就行

七、图解 Promise 内部原理

我自己写了个简化版,帮你理解 Promise 到底干了啥:

function Promise(fn) {
  this.state = 'pending'  // 一开始是准备状态
  this.arr = [foo]        // 存回调函数

  const resolve = (res) => {
    this.state = 'resolved'  // 变成成功状态
    // 然后执行之前存好的回调
    // foo(res)
  }

  const reject = () => {
    // 类似,变成失败状态
  }

  fn(resolve, reject)  // 执行传进来的函数
}

new Promise((resolve, reject) => {
  resolve()
})

执行流程

  1. new Promise() → 得到一个状态为 pending 的对象
  2. .then(foo) → 把 foo 存起来(还没执行)
  3. 时间到了,resolve 被触发
  4. Promise 内部状态改为 resolved
  5. 把之前存的 foo 拿出来执行

就这么简单。Promise 本质上就是个状态机 + 回调队列


八、Promise.all 和 Promise.race

Promise.all

所有 Promise 都完成了再干活。

// 三个不相关的接口,一起请求
const p1 = fetch('/api/user');
const p2 = fetch('/api/news');
const p3 = fetch('/api/products');

Promise.all([p1, p2, p3]).then(([user, news, products]) => {
  // 三个都拿到了,再一起渲染页面
});

一个失败,全部失败。

Promise.race

谁先完成就用谁的。

// 请求超时处理
Promise.race([
  fetch('/api/data'),
  new Promise((_, reject) => setTimeout(() => reject('超时了'), 3000))
]);

九、面试常问

输出顺序题

console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);

答案是:1, 4, 3, 2

原因是 JS 的事件循环:

  1. 同步代码先执行(1 和 4)
  2. 微任务(Promise 的 then)比宏任务(setTimeout)先执行
  3. 所以 3 在 2 前面

Promise 状态能变几次?

一次。从 pending 变成 resolved 或 rejected 后,就定死了,不能改。


总结

  • 异步任务用回调解决,但多了就成了回调地狱
  • Promise 让异步变扁平,链式调用 .then()
  • Promise 三种状态:pending → resolved / rejected,只能变一次
  • .then() 存回调,resolve() 触发回调
  • 学完 Promise 下一步学 async/await,写起来更像同步代码