Promise解析

它是什么

Promise可以看做一个承诺------当前某事还未解决,但未来一定会解决,常用来处理异步操作。

举个例子:你点了一份菜,商家承诺半小时内上菜。这就是Promise。

它解决了什么问题

Promise的链式调用完美解决了回调地狱的问题。

js 复制代码
function doSomething(){
    console.log("doSomething");
    
    function doSomething2(){
        console.log("doSomething2");
        
        function doSomething3(){
            console.log("doSomething3");
        }
    }
}

例如这个经典的回调地狱,当函数的嵌套层级足够多时,代码的可阅读性、可维护性难度会成倍增长。如果改成Promise链式调用,就会非常清晰。

js 复制代码
doSomething()
    .then(() => {
        console.log("doSomething");
        return doSomething2();
    })
    .then(() => {
        console.log("doSomething2");
        return doSomething3();
    })
    .then(() => {
        console.log("doSomething3");
    })

在小程序开发中,将小程序API改为Promise形式进行链式调用非常有用,否则就容易产生回调地狱。

它是怎么工作的

状态流转

Promise有3种状态:

  • pending:待定,事情还未处理完成。
  • fulfilled:已兑现,事情成功处理完成。
  • rejected:已拒绝,事情处理失败。

同样是点菜的例子:

  • 点菜后到上菜前都是pending状态。此时后厨在备菜,或者服务员在忙,还未上你这一桌的菜。
  • 服务员把菜端到桌上,此时是fulfilled状态。菜品已经成功做出并端上桌。
  • 服务员过来告诉你因为生意太好,你点的菜已经卖完了。此时是rejected状态。出了一点小意外,你点的菜没吃上。

此外,还有两种常说的状态------resolved(已解决)、settled(已敲定)------不是Promise的基本状态,是归类用的。一起看看在点菜的例子中是怎么体现的。

  • 点菜后到上菜前都是pending状态。
  • 后厨做好菜,等待服务员来端走,此时是resolved状态。
  • 服务员把菜端到桌上,此时是fulfilled状态。
  • 服务员上菜过程中摔了一跤,菜打翻了,此时是rejected状态。
  • 无论是服务员成功把菜端到桌上还是打翻了菜,通知到你都是settled状态。

settled状态非常好理解,在这个状态时,点菜的结果已经确定并且不会再发生改变 ------成功上菜或菜打翻了。resolved状态怎么理解呢?我们知道,在餐厅里,后厨工作人员和服务员通常不是同一个人,因此点餐后的处理是分为了两个环节------后厨备菜、服务员上菜。后厨做好菜通知到服务员(后厨的工作已完成),等待服务员上菜给顾客(整个点菜过程完成)。这种一个环节完成,依赖另一个环节完成才能最终完成 的状态就叫resolved。如果后厨和服务员是同一个人,做好菜立马上菜成功,这个时候resolved基本就等价于fulfilled

js 复制代码
const waiter = new Promise((resolve, reject) => {
  setTimeout(() => resolve("服务员成功上菜"), 1000); // 执行这个,最终是fulfilled状态
  // 或
  // setTimeout(() => reject("服务员打翻了菜"), 1000); // 执行这个,最终是rejected状态
});

const chef = new Promise((resolve) => {
  console.log("厨师备菜完成,等待上菜");
  resolve(waiter); // 此时是resolved状态,依赖服务员上菜环节
});

// 在.then()或.catch()被执行前,是pending状态
// 无论执行.then()还是.catch(),都是settled状态
chef
  .then((res) => console.log("点菜成功:", res))
  .catch((err) => console.log("点菜失败:", err));

链式调用

在链式调用中,常用到三个函数:.then().catch().finally(),一起来看看这三个函数。

Promise.prototype.then

.then()接收两个参数:onFulfilled、onRejected,分别用于处理前一个Promise的fulfilledrejected状态。

js 复制代码
const p1 = new Promise((resolve, reject) => {
  resolve("p1 resolve");
  // 或
  // reject("p1 reject");
});

p1.then(
  (result) => {
    console.log(result);
  },
  (error) => {
    console.log(error);
  },
);

比较特殊的是,如果参数onFulfilled不是一个函数,那将仅做向前传递数据的作用。如果参数onRejected不是一个函数,将抛出接收到的异常。

js 复制代码
const p1 = new Promise((resolve, reject) => {
  resolve("p1 resolve");
});

const p2 = p1.then("p2 resolve"); // 此处将被替换为恒等函数:(result) => result

p2.then((result) => {
  console.log(result); // p1 resolve
});

const p3 = new Promise((resolve, reject) => {
  reject("p3 reject");
});

const p4 = p3.then("p4 resolve", "p4 reject"); // 此处将被替换为恒等函数:(error) => { throw error; }

p4.then(
  (result) => {
    console.log(result);
  },
  (error) => {
    console.log(error); // p3 reject
  },
);

.then().catch().finally()的基础:

  • .catch()等价于.then(undefined,onRejected())
  • .finally()等价于.then(onFinally(),onFinally())

Promise.prototype.catch

.catch()接收一个onRejected作为参数。当且仅当Promise的状态为rejected时,.catch()才会被执行。

.catch()里可以返回新的Promise状态,以使下一段链式调用进入不同的处理分支。

js 复制代码
const p1 = Promise.reject("p1 抛出异常");

p1.catch((err) => {
  console.log(err);
  return Promise.reject();
})
  .then(() => console.log("next then"))
  .catch(() => console.log("next catch"));
  
// p1 抛出异常
// next catch

.catch()中没有返回新的Promise状态时,默认为fulfilled状态,可以在.catch()后链式调用.then()

最重要的一点是:在链式调用时,只需在链条最后调用一次.catch(),即可捕获任一节点的错误。

js 复制代码
const p1 = new Promise((resolve) => {
  resolve("p1 resolve");
});

const p2 = new Promise((resolve, reject) => {
  reject("p2 reject");
});

const p3 = new Promise((resolve) => {
  resolve("p3 resolve");
});

p1.then((result1) => {
  console.log(result1); // p1 resolve
  return p2;
})
  .then((result2) => {
    console.log(result2);
    return p3;
  })
  .then((result3) => {
    console.log(result3);
  })
  .catch((error) => {
    console.log(error); // p2 reject
  });

Promise.prototype.finally

.finally()接收一个onFinally作为参数。在.finally()中,Promise的状态已经确定,要么是fulfilled,要么是rejected

同时,在onFinally()做的任何操作不会改变Promise的状态。

js 复制代码
const doSomething = new Promise((resolve) => resolve("success"));

doSomething
  .then((res) => console.log(res))
  .finally(() => {
    Promise.reject("finally抛出异常");
    setTimeout(() => {
     console.log('finally抛出异常后',doSomething);
    }, 1000);
  });

适用于无需依赖Promise最终状态做相应处理的场景。

js 复制代码
const waiter = new Promise((resolve) => {
  setTimeout(() => resolve("服务员成功上菜"), 1000);
});

const chef = new Promise((resolve) => {
  console.log("厨师备菜完成,等待上菜");
  resolve(waiter);
});

const isWaiting = ref(false);

chef
  .then((res) => console.log("点菜成功:", res))
  .catch((err) => console.log("点菜失败:", err))
  .finally(() => {
    isWaiting.value = false;
  });

Promise并发

Promise.all

Promise.all接收一个Promise数组作为参数。当且仅当所有Promise都是fulfilled状态时,可以得到包含所有Promise返回值的数组。否则返回第一个rejected的Promise的错误信息。

js 复制代码
const p1 = new Promise((resolve, reject) => {
  resolve("p1 resolve");
});

const p2 = new Promise((resolve, reject) => {
  resolve("p2 resolve");
});

const p3 = new Promise((resolve, reject) => {
  resolve("p3 resolve");
  // 或
  // reject("p3 reject");
});

Promise.all([p1, p2, p3])
  .then((results) => {
    console.log(results); // 如果p3执行了resolve(),打印:['p1 resolve', 'p2 resolve', 'p3 resolve']
  })
  .catch((error) => {
    console.log(error); // 如果p3执行了reject(),打印: p3 reject
  });

Promise.allSettled

Promise.allSettled接收一个Promise数组作为参数。返回一个包含所有Promise执行结果 的数组(不会执行.catch)。

js 复制代码
const p1 = new Promise((resolve, reject) => {
  resolve("p1 resolve");
});

const p2 = new Promise((resolve, reject) => {
  resolve("p2 resolve");
});

const p3 = new Promise((resolve, reject) => {
  reject("p3 reject");
});

Promise.allSettled([p1, p2, p3]).then((results) => {
  console.log(results);
});

// [
//  { "status": "fulfilled","value": "p1 resolve" },
//  { "status": "fulfilled","value": "p2 resolve" },
//  { "status": "rejected","reason": "p3 reject" }
// ]

Promise.any

Promise.any接收一个Promise数组作为参数。只要有一个Promise是fulfilled状态,就返回该Promise的返回值

所有Promise都是rejected状态时,抛出异常:AggregateError: All promises were rejected

js 复制代码
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("p1 resolve");
  }, 300);
});

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("p2 resolve");
  }, 100);
});

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("p3 reject");
  }, 200);
});

Promise.race([p1, p2, p3]).then((result) => {
  console.log(result); // p2执行更快,打印: p2 resolve
});

Promise.race

Promise.race接收一个Promise数组作为参数。只要有一个Promise执行完成(无论是fulfilled还是rejected),就执行对应的.then()或.catch()

js 复制代码
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("p1 resolve");
  }, 300);
});

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("p2 resolve");
  }, 100);
});

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("p3 reject");
  }, 50);
});

Promise.race([p1, p2, p3])
  .then((result) => {
    console.log(result); // 不会执行
  })
  .catch((error) => {
    console.log(error); // p3更快执行完成,打印:p3 reject
  });
相关推荐
gogoing1 小时前
Prettier 配置说明
前端·javascript
前端毕业班1 小时前
uni-app onShareAppMessage hook 原理分析
前端·javascript
gogoing1 小时前
Babel 配置与工具
前端·javascript
晚风予卿云月1 小时前
【Linux】Linux2.6 O(1)调度器超详解 | 进程切换+内核链表 | 面试必背
linux·运维·面试
蜡台1 小时前
Vue3 Hook 与 Store 状态管理:深度解析与选型指南
前端·javascript·vue.js
存在的五月雨1 小时前
项目中 Vitest 配置详解:vitest.config.ts
开发语言·javascript·vue.js
野犬寒鸦2 小时前
Claude Code:终端AI编程助手全指南(附带指令全讲解)
开发语言·后端·面试·ai编程
淡笑沐白2 小时前
JavaScript零基础到精通
开发语言·javascript·ecmascript
openKaka_3 小时前
beginWork 的第一站:HostRoot 如何把 App 接入 Fiber 树
前端·javascript·react.js