目录

Promise 基础使用

用一个餐厅点餐场景去理解Promise

promise定义

Promise 的三种状态

理解 then 链式调用

then 为什么可以一直链下去?

catch 本质是什么?

finally 又是什么?

完整结构模型

理解 async/await 本质

定义

async

await

手写简版 Promise

Promise 静态方法(all / race / allSettled / any)

Promise.all

Promise.race()

Reference

Promise 基础使用

在 ES6 之前,JavaScript 处理异步逻辑主要依赖回调函数。随着业务复杂度提升,回调嵌套容易形成“回调地狱”,代码可读性和可维护性都很差。

比如我们有以下一个场景

  • 先获取用户信息
  • 再根据用户信息获取订单列表
  • 再根据订单获取物流信息
getUser(userId, function (user) {
  getOrders(user.id, function (orders) {
    getLogistics(orders[0].id, function (logistics) {
      console.log("最终物流信息:", logistics);
    }, function (err) {
      console.error("获取物流失败:", err);
    });
  }, function (err) {
    console.error("获取订单失败:", err);
  });
}, function (err) {
  console.error("获取用户失败:", err);
});

上述代码就存在层层嵌套,结构越来越深,可读性以及维护性差,因此为了解决这些问题,ES6 引入了 Promise

用一个餐厅点餐场景去理解Promise

假设你走进一家餐厅,对服务员说:

“我要一份牛肉面。”

在这一刻,发生了几件事情:

你的订单被创建,你拿到了一张小票

这张“小票”,就相当于一个 Promise 对象。 此时你也处于等待状态。

然后你对服务员说,面做好了叫我,也就是你留了一个联系方式,也就是你定义.then()

之后厨房这边做好面了,就执行了resolve, 你的订单状态也就从pending 变成了 → fulfilled 表示你的牛肉面做好了

那么就会通知你说你的面做好了,那么你就可以执行.then() 。

const myPromise = new Promise((resolve) => {
  console.log("1. 下单");
   // 厨房开始做面
  
  setTimeout(() => {       //过了一段时间面做好了
    console.log("3. 面做好了");
    resolve(); // 执行resolve
  }, 1000);
});

myPromise.then(() => { 
  console.log("4. 开吃!");
});

console.log("2. 等待中...");

promise定义

综合上述,我们现在正式给出一个promise的定义。

Promise 对象表示异步操作最终的完成(或失败)以及其结果值。它允许你将处理程序与异步操作的最终成功值或失败原因关联起来。

也就是当我们执行一个异步操作时,它不会立即返回结果,而是返回一个 Promise 对象。等异步操作完成后,Promise 会把最终结果(成功或失败)交给我们处理。

Promise 的三种状态

一个 Promise 在生命周期中只会处于以下三种状态之一:

  1. pending(待定)

    初始状态,既没有成功,也没有失败。

  2. fulfilled(已兑现)

    表示异步操作成功完成,并得到一个结果。

  3. rejected(已拒绝)

    表示异步操作失败,并得到一个错误原因。

需要注意的是:

Promise 的状态一旦从 pending 变为 fulfilled 或 rejected,就不会再发生改变。


理解 then 链式调用

Promise 之所以能解决“回调地狱”,核心就在于:

then 会返回一个新的 Promise,从而形成链式调用。


then 为什么可以一直链下去?

每一次调用 then() 时:

  • 这个.then()都会创建一个新的 Promise
  • 新 Promise 的状态由当前 then 的返回值决定

看这段代码:

newPromise(resolve => {resolve(1);
})
.then(res => {// res = 1 
	return res +1;
})
.then(res => {// res = 2
	console.log(res);
});

执行过程是:

  1. 第一个 Promise resolve(1)
  2. 第一个 then 收到 1
  3. 返回 2
  4. 这个 2 会被自动包装成一个新的 Promise
  5. 第二个 then 收到 2

这个返回普通值会发生什么?

.then(res => {
	return 2;
})

等价于:

.then(res => {
	return Promise.resolve(2);
})

也就是说:

then 返回普通值时,会被自动包装成一个 fulfilled 状态的 Promise。

这也是为什么链条可以持续下去。


如果返回的是 Promise 呢?

.then(res => {
	return new Promise(resolve => {
		setTimeout(() => {
			resolve(3);
    },1000);
  });
})

这时候:

  • 下一个 then 会等待这个 Promise 完成
  • 完成后这个Promise后再继续执行

如果发生错误

Promise 不仅有 resolve,还有 reject

当执行:

reject("失败");

或者:

thrownewError("出错了");

都会使当前 Promise 进入 rejected 状态。

这时:

  • 后续的 then 会被跳过
  • 会直接进入最近的 catch

例如:

newPromise((resolve, reject) => {reject("失败");
})
.then(res => {console.log("不会执行");
})
.catch(err => {console.log("进入 catch:", err);
});

输出:

进入catch: 失败,不会去执行中间那个 .then()

catch 本质是什么?

其实:

promise.catch(fn)

等价于:

promise.then(null, fn)

也就是说:

catch 只是 then 的语法糖,用来专门处理 rejected 状态。


finally 又是什么?

Promise 还有一个方法:

.finally()

它的特点是:

不管 Promise 成功还是失败,都会执行。

newPromise((resolve, reject) => {resolve("成功");
})
.then(res => {console.log(res);
})
.catch(err => {console.log(err);
})
.finally(() => {console.log("结束了");
});

输出:

成功
结束了

如果失败:

失败
结束了

完整结构模型

链式调用的本质结构其实是:

Promise1
   ↓then()
Promise2
   ↓then()
Promise3
   ↓catch()
Promise4
   ↓finally()
Promise5

每一次调用:

  • 都会生成一个新的 Promise
  • 状态会在链条中不断传递

理解 async/await 本质

目前,随着我们的.then太多了,我们又发现好像这个代码不太直观, 我们希望能更好控制数据流

getUser()
  .then(user => getOrders(user.id))
  .then(orders => getDetail(orders[0].id))
  .then(detail => {
    console.log(detail);
  })
  .catch(err => {
    console.log(err);
  });

因此,我们有了async

async function getData() {
  try {
    const user = await getUser();
    const orders = await getOrders(user.id);
    const detail = await getDetail(orders[0].id);

    console.log(detail);
  } catch (err) {
    console.log(err);
  }
}

定义

async/await 是 Promise 链式调用的语法糖。

async

首先,async是用来声明一个函数,让一个函数的返回结果永远返回Promise对象

async function test() {
  return 1;
}
等价
function test() {
  return Promise.resolve(1);
}
// 因此对于上面这个test()函数,我们知道是返回一个promise对象,就可以写
test().then(res => console.log(res));

await

await 本质是暂停当前函数,等待 Promise 完成。

  • 它只暂停当前 async 函数
  • 不会阻塞整个线程
await x

Promise.resolve(x).then(...)

其实是,await会把await后面的内容变成微任务处理,然后根据js的事件循环流程执行。比如以下,

async function test() {
  console.log(1);
  await Promise.resolve();
  ... // 这里内容以及下面的console.log(2) 这块代码块都会变成一个微任务 也就是加入微任务队列
  console.log(2);  
}
console.log(3);
test();
console.log(4);

print
3
1
4
2

手写简版 Promise

最后我们实现一个简单的Promise

class MyPromise {
  constructor(executor) {
    // Promise 初始状态必须是 pending
    // 因为 Promise 一创建时还不知道结果
    this.state = "pending";

    // 成功后的值
    this.value = undefined;

    // 失败后的原因
    this.reason = undefined;

    // 用数组存储成功回调
    // 因为当状态是 pending 时,then 可能会被多次调用
    this.onFulfilledCallbacks = [];

    // 用数组存储失败回调
    this.onRejectedCallbacks = [];

    // resolve 函数
    const resolve = (value) => {

      // 状态不可逆,只能从 pending 改一次
      if (this.state === "pending") {

        this.state = "fulfilled";
        this.value = value;

        // 状态改变后,执行之前存储的所有成功回调
        this.onFulfilledCallbacks.forEach(fn => fn());
      }
    };

    // reject 函数
    const reject = (reason) => {

      if (this.state === "pending") {

        this.state = "rejected";
        this.reason = reason;

        // 执行之前存储的失败回调
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    };

    // !!!执行入口:executor 需要立即执行
    // 因为 Promise 构造函数是同步执行的
    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error); // 如果执行器报错,直接 reject
    }
  }

  // 定义then 
  then(onFulfilled, onRejected) {

    // 10️⃣ then 必须返回一个新的 Promise
    // 这是链式调用的核心
    return new MyPromise((resolve, reject) => {

      if (this.state === "fulfilled") {

        // then 必须异步执行(微任务)
        queueMicrotask(() => {
          try {
            const result = onFulfilled(this.value);
            resolve(result);
          } catch (error) {
            reject(error);
          }
        });
      }

      if (this.state === "rejected") {

        queueMicrotask(() => {
          try {
            const result = onRejected(this.reason);
            resolve(result);
          } catch (error) {
            reject(error);
          }
        });
      }
      if (this.state === "pending") {

        // 如果还是 pending,说明 resolve 还没执行
        // 先把回调存起来

        this.onFulfilledCallbacks.push(() => {
          queueMicrotask(() => {
            try {
              const result = onFulfilled(this.value);
              resolve(result);
            } catch (error) {
              reject(error);
            }
          });
        });

        this.onRejectedCallbacks.push(() => {
          queueMicrotask(() => {
            try {
              const result = onRejected(this.reason);
              resolve(result);
            } catch (error) {
              reject(error);
            }
          });
        });
      }

    });
  }

  //  catch 本质是 then(null, onRejected)
  catch(onRejected) {
    return this.then(null, onRejected);
  }

  //  finally 不改变状态,只执行回调
  finally(callback) {
    return this.then(
      value => {
        callback();
        return value;
      },
      reason => {
        callback();
        throw reason;
      }
    );
  }
}

Promise 静态方法(all / race / allSettled / any)

Promise.all

当你有多个异步任务:

  • 全部成功 → 才算成功
  • 只要有一个失败 → 整体失败
Promise.all([
  fetchUser(),
  fetchOrders(),
  fetchDetail()
]).then(res => {
  console.log(res);
}).catch(err => {
  console.log(err);
});
// 全部 fulfilled 才 resolve

Promise.race()

多个异步任务:

  • 谁先完成
  • 用谁的结果
Promise.race([
  fetchData(),
  timeoutPromise()
]).then(res => {
  console.log(res);
});
// 这个fetchData 与 timeoutPromise() 谁先执行完用谁的结果

其余静态方法可以查看文档

总结

Promise 的核心并不复杂:

  • Promise有三种状态

  • 内部维护回调队列

  • 状态不可逆

  • 通过返回新 Promise 实现链式调用

async/await 也就是 Promise 的语法糖。

Reference

JavaScript Promise | 菜鸟教程

Promise - JavaScript | MDN

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐