Promise详解
1.全面梳理JS异步编程Promise方案,详细解读Promise标准的方方面面的细节,分析Promise解决回调地狱的优势2.总结async/await语法糖对Promise传统编程方案的优化。
Promise详解
Promise是一种异步编程方案。Promise 对象表示异步操作最终的完成(或失败)以及其结果值。
一个 Promise 是一个代理,它代表一个在创建 promise 时不一定已知的值。它允许你将处理程序与异步操作的最终成功值或失败原因关联起来。这使得异步方法可以像同步方法一样返回值:异步方法不会立即返回最终值,而是返回一个 promise,以便在将来的某个时间点提供该值。
状态
一个Promise有以下三种状态:
pending:待定状态,既不是成功,也不是失败状态。settled:敲定状态fulfilled:已兑现状态,操作成功完成。rejected:已拒绝状态,操作失败。
Promise对象内部运行的变化:
- 当
new Promise()后,表示Promise进入pending初始化状态,准备就绪,等待运行。 - 一旦
Promise实例运行成功或失败,实例状态就会变更为敲定状态(fulfilled或者rejected),此时状态无法再变更。
使用
在使用 Promise 时,通常会调用其 then() 方法来处理异步操作成功的结果,或者调用 catch() 方法来处理出错信息。同时,Promise 还提供了一些静态方法,如 Promise.resolve()、Promise.reject() 等用于快速创建一个 Promise 实例。
Thenable对象
在 Promise 成为 JavaScript 语言的一部分之前,JavaScript 生态系统已经有了多种 Promise 实现。尽管它们在内部的表示方式不同,但至少所有类 Promise 的对象都实现了 Thenable 。
简单来说,thenable 对象就是实现了 then() 方法的普通对象,该方法被调用时需要传入两个回调函数,一个用于 Promise 被兑现时调用,一个用于 Promise 被拒绝时调用。Promise 也是 thenable 对象。
const aThenable = {
then(onFulfilled, onRejected) {
...
},
};
构造Promise
原型
const promise1 = new Promise(executor);
const promise1 = new Promise((resolve, reject)=>{...});
参数
executor,在构造函数中执行的function。它接收两个函数作为参数:resolve(兑现回调)和reject(拒绝回调)。executor中抛出的任何错误都会导致Promise被拒绝,并且返回值将被忽略。
注:
executor 的对 Promise 的状态的影响:
executor函数中的return语句仅影响控制流程,调整函数某个部分是否执行,但不会影响Promise的兑现值。如果executor函数退出,且未来不可能调用resolve或reject(例如,没有安排异步任务),那么Promise将永远保持待定状态。- 如果在
executor函数中抛出错误,则Promise将被拒绝,除非resolveFunc或rejectFunc已经被调用。 executor的**返回值将作为resolve的参数,抛出的错误将作为reject的参数**。
创建后的Promis实例中有then(),catch()等方法:
then():方法需传递一个参数方法,此参数方法将作为executor的resolve,在Promise被兑现后执行。catch():方法需传递一个参数方法,此参数方法将作为executor的reject,在Promise被拒绝后执行。
示例1
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("foo");
}, 300);
});
promise1.then((value) => { //then方法中的传入的参数方法将作为resolve
console.log(value);
});
// 0.3秒后输出: "foo"
示例2
const readFilePromise = (path) =>
new Promise((resolve, reject) => {
readFile(path, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
readFilePromise("./data.txt")
.then((result) => console.log(result))
.catch((error) => console.error("读取文件失败"));
resolve 和 reject 回调仅在 executor 函数的作用域内可用,在构造 promise 之后无法访问它们。
返回值
当通过 new 关键字调用 Promise 构造函数时,它会返回一个 Promise 对象。当 resolve 或者 reject 被调用时,该 promise 对象就会固定为兑现(fulfilled)或拒绝(reject),并立即将resolve或reject函数封装为任务推入微队列。
请注意,如果你调用 resolve 或 reject 时传入另一个 promise 对象作为参数,可以说该 promise 对象“已解决”,但仍未“敲定(settled)”。有关更多解释,请参阅 Promise 描述。
Promise编程与传统异步编程区别
以模拟获取用户信息->获取用户订单->获取用户订单详情的三层异步操作为例。
不使用 Promise:回调地狱的问题
传统异步编程依赖回调函数,每一步异步操作的结果需要在回调中处理,多层嵌套会导致代码像 “金字塔” 一样臃肿(回调地狱)。
// 模拟异步(回调模式):获取用户信息
function getUser(userId, callback) {
setTimeout(() => {
console.log("获取用户信息成功");
callback({ id: userId, name: "张三" }); // 回调返回用户数据
}, 1000);
}
// 模拟异步(回调模式):根据用户信息获取订单编号
function getOrders(user, callback) {
setTimeout(() => {
console.log("获取订单成功");
callback([{ orderId: 1001, userId: user.id }]); // 回调返回订单数据
}, 1000);
}
// 模拟异步(回调模式):根据订单获取详情
function getOrderDetail(order, callback) {
setTimeout(() => {
console.log("获取订单详情成功");
callback({ orderId: order.orderId, product: "手机" }); // 回调返回详情
}, 1000);
}
// 业务逻辑:三层嵌套调用
getUser(1, (user) => { //1.获取用户数据
getOrders(user, (orders) => { //2.根据用户数据获取订单编号
getOrderDetail(orders[0], (detail) => { //3.根据订单编号获取订单详情
console.log("最终结果:", detail); // 输出:{ orderId: 1001, product: "手机" }
});
});
});
使用 Promise:链式调用的优势
Promise 通过状态管理和链式调用,将嵌套的回调改为线性代码,解决了回调地狱的问题。
// 改造为返回 Promise 的异步函数
function getUser(userId) {
return new Promise((resolve) => {
setTimeout(() => {
console.log("获取用户信息成功");
resolve({ id: userId, name: "张三" }); // 成功时用 resolve 返回结果
}, 1000);
});
}
function getOrders(user) {
return new Promise((resolve) => {
setTimeout(() => {
console.log("获取订单成功");
resolve([{ orderId: 1001, userId: user.id }]);
}, 1000);
});
}
function getOrderDetail(order) {
return new Promise((resolve) => {
setTimeout(() => {
console.log("获取订单详情成功");
resolve({ orderId: order.orderId, product: "手机" });
}, 1000);
});
}
// 业务逻辑:链式调用
getUser(1)
.then((user) => getOrders(user)) // 第一步成功后,调用第二步
.then((orders) => getOrderDetail(orders[0])) // 第二步成功后,调用第三步
.then((detail) => {
console.log("最终结果:", detail); // 输出:{ orderId: 1001, product: "手机" }
});
链式调用的核心
then、catch、finally 都会返回一个新的 promise 对象,这是链式调用的基础。新 promise 的状态由回调函数的返回值决定:
- 若回调返回非
promise值(如数字、字符串),新Promise状态为resolve,结果为该值。 - 若回调返回另一个
promise,新promise会 “继承” 其状态和结果。 - 若回调抛出错误(
throw new Error(...)),新 Promise 状态为rejected,错误为抛出的值。
实例方法
then()
正如前面所说,实例方法then()最多接受两个参数:用于 promise 兑现和拒绝情况的回调函数。
它会立即返回一个等效的 promise 对象,允许你链接到其他 promise 方法,从而实现链式调用。
注:
-
then()方法用于为 promise 对象的敲定设置回调函数。它是Promise的基本方法:thenable规范要求所有类Promise对象都提供一个then()方法,并且catch()和finally()方法都会通过调用该对象的then()方法来工作。 -
then()方法返回一个新的promise对象。如果在同一promise对象上两次调用then()方法(而不是链式调用),则该 promise 对象将具有两对处理方法。附加到同一promise对象的所有处理方法总是按照它们添加的顺序调用。 -
每次独立调用
then()方法返回promise对象开始了独立的链,不会等待彼此的敲定。
catch()
实例 catch() 方法用于注册一个在 promise 被拒绝时调用的函数。
catch(reject)是then(undefined,reject)的简写形式,两者等价。
finally()
实例 finally() 方法用于注册一个在 promise 敲定时调用的函数。这可以让你避免在 promise 的 then() 和 catch()处理器中编写重复代码。
例:
function checkMail() {
return new Promise((resolve, reject) => {
if (Math.random() > 0.5) {
resolve("Mail has arrived");
} else {
reject(new Error("Failed to arrive"));
}
});
}
checkMail()
.then((mail) => {
console.log(mail);
})
.catch((err) => {
console.error(err);
})
.finally(() => {
console.log("Experiment completed");
});
//Error: Failed to arrive
//"Experiment completed"
注:在 finally 回调函数中抛出错误(或返回被拒绝的 promise)仍会导致当前的 promise 被拒绝。
静态方法
resolve()
resolve(value)方法以给定值“解决(resolve)”一个 promise,返回一个状态为 resolve的 promise对象。
-
value是非thenable对象,返回一个以该值兑现的promise。 -
value是一个thenable对象,Promise.resolve()将其then()方法封装为任务推入微队列; -
value本身就是一个promise,那么该promise将被返回。
该方法将嵌套的类 promise 对象(例如,一个将被兑现为另一个 promise 对象的 promise 对象)展平,转化为单个 promise 对象,其兑现值为一个非 thenable 值。
传入普通参数
Promise.resolve("成功").then(
(value) => {
console.log(value); // "成功"
},
(reason) => {
// 不会被调用
},
);
传入promise对象参数
Promise.resolve() 方法传入一个promise参数时,会重用已存在的 Promise 实例。它将返回同一 Promise 实例,而不会创建一个新封装对象。
const original = Promise.resolve(33);
const cast = Promise.resolve(original);
cast.then((value) => {
console.log(`值:${value}`); //值:33
});
console.log(`original === cast ? ${original === cast}`); //original === cast ? true
传入 thenable 对象参数
// Thenable 在成功回调之前抛出异常
// Promise 被拒绝
const thenable = {
then(onFulfilled) {
throw new TypeError("抛出异常");
onFulfilled("Resolving");
},
};
const p2 = Promise.resolve(thenable);
p2.then(
(v) => {
// 不会被调用
},
(e) => {
console.error(e); // TypeError: 抛出异常
},
);
// Thenable 在成功回调之后抛出异常
// Promise被解决
const thenable = {
then(onFulfilled) {
onFulfilled("解决");
throw new TypeError("Throwing");
},
};
const p3 = Promise.resolve(thenable);
p3.then(
(v) => {
console.log(v); // "解决"
},
(e) => {
// 不会被调用
},
);
reject()
**Promise.reject(error)**方法返回状态为 rejected 的 promise 对象,错误为 error。
function resolved(result) {
console.log("Resolved");
}
function rejected(result) {
console.error(result);
}
Promise.reject(new Error("fail")).then(resolved, rejected);
// Expected output: Error: fail
try()
该静态方法接受一个任意类型的回调函数(无论其是同步或异步,返回数据或抛出异常),并将其结果封装成一个 promise
Promise.try(func)
Promise.try(func, ..arg)
参数
func:使用提供的参数(arg1、arg2、…、argN)同步调用的函数。它可以做任何事情——要么返回一个值、抛出一个错误或者返回一个promise。..arg:传入给func的参数列表。
all()和any()
all()
Promise.all() 静态方法接受一个 Promise 可迭代对象iterable(具有foreach()方法可枚举元素,如数组)作为输入,并返回一个 Promise。
- 当输入的所有
promise都被兑现(包括传递了空的可迭代对象),返回的promise也将被兑现,并带有包含所有兑现值的数组。如果iterable包含非promise值,这些值将被忽略,但仍包含在携带的promise的兑现数组中。 - 如果输入的
iterable中的promise任何一个被拒绝,则返回的promise将被拒绝,并带有第一个被拒绝的原因。
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, "foo");
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
// Expected output: Array [3, 42, "foo"]
any()
Promise.any() 静态方法接受一个 promise 可迭代对象iterable(具有foreach()方法可枚举元素,如数组)作为输入,并返回一个 Promise。
- 当输入的
iterable中的promise任何一个被兑现,这个返回的promise将会兑现,并带有第一个兑现的值。 - 当输入的所有
promise都被拒绝(包括传递了空的可迭代对象),并带有包含所有拒绝原因的数组。如果iterable包含非promise值,这些值将被忽略,但仍包含在携带的promise的拒绝原因数组中。
const promise1 = Promise.reject(0);
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, "quick"));
const promise3 = new Promise((resolve) => setTimeout(resolve, 500, "slow"));
const promises = [promise1, promise2, promise3];
Promise.any(promises).then((value) => console.log(value));
// Expected output: "quick"
allSettled()
Promise.allSettled() 静态方法接收 promise 可迭代对象iterable(包括传入空的可迭代对象时),并返回一个单独的 promise。当所有输入的 Promise 都已敲定时,返回的 promise 将被兑现,并带有描述每个 promise 结果的对象数组。
**allSettled()**与all()的最大区别就是:allSettled()不管兑现拒绝与否,都带有描述每个promise执行结果的对象数组,这意味着我们可掌握每个异步操作的执行情况,根据执行情况进行后续更灵活的操作(如:即使部分操作失败,也保留成功的结果)。
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) =>
setTimeout(reject, 100, "foo"),
);
const promises = [promise1, promise2];
Promise.allSettled(promises).then((results) =>
results.forEach((result) => console.log(result.status)),
);
// Expected output:
// "fulfilled"
// "rejected"
兑现携带的results数组中的元素对象都含有一下属性:
status:一个字符串,要么是"fulfilled",要么是"rejected",表示promise的最终状态。value:仅当status为"fulfilled",才存在,promise兑现的值。reason:仅当status为"rejected",才存在,promsie拒绝的原因。
async/await
async/await 是 JavaScript 中基于 Promise 的语法糖,它让异步代码的写法更接近同步代码,进一步简化了异步逻辑的处理。可以理解为:async/await 是 Promise 的 “语法升级”,完全依赖 Promise 工作。
async/await做了哪些升级?
痛点:开发者需要的异步操作往往是一个函数,传统的Promise编程总是需要将异步操作封装进一个promise对象中,这编写了很多单调而重复的代码。
解决:使用async可以直接将一个函数标记为一个异步操作,底层会自动封装成promise对象。
痛点:为了实现Promise的链式编程,Promise的每个实例方法或静态方法都会返回一个promise对象,对开发者而言,更关心的是兑现值或拒绝原因。
解决:使用await可以直接获取返回值那样获取兑现值,被拒绝则直接抛出错误。
async 关键字
- 用于修饰函数(普通函数、箭头函数、对象方法等),表示该函数返回一个 Promise 对象。
- 函数内部的返回值会被自动包装成
Promise.resolve(返回值);若抛出错误,则会被包装成Promise.reject(错误)。
示例
// 普通函数
async function fn1() {
return "成功结果"; // 等价于 return Promise.resolve("成功结果")
}
// 箭头函数
const fn2 = async () => {
throw new Error("出错了"); // 等价于 return Promise.reject(new Error("出错了"))
};
// 调用 async 函数,返回的是 Promise
fn1().then(result => console.log(result)); // 输出 "成功结果"
fn2().catch(error => console.log(error.message)); // 输出 "出错了"
await 关键字
- 只能在
async函数内部使用,用于等待一个Promise完成。 - 当
await后面跟一个Promise时,会暂停当前async函数的执行,直到该Promise敲定。 - 若
Promise被兑现,await会返回其兑现结果;若被拒绝,则会将拒绝原因封装为错误抛出(可被try/catch捕获)。
用async/await改造一下前面的模拟获取用户信息->获取用户订单->获取用户订单详情的三层异步操作:
// 基于之前的 getUser / getOrders / getOrderDetail(返回 Promise)
async function getResult() {
try {
// 等待 getUser 完成,拿到用户信息
const user = await getUser(1);
// 等待 getOrders 完成,拿到订单列表
const orders = await getOrders(user);
// 等待 getOrderDetail 完成,拿到详情
const detail = await getOrderDetail(orders[0]);
console.log("最终结果:", detail); // 输出订单详情
return detail; // 返回结果会被包装成 Promise
} catch (error) {
// 捕获所有 await 过程中可能出现的错误
console.log("出错了:", error.message);
}
}
// 调用 async 函数
getResult();
这段代码的逻辑和之前用 then 链式调用完全一致,但写法更像同步代码,可读性大幅提升,使得异步代码和同步代码在书写时几乎没有区别。
注意事项
-
await只能在async函数中使用,否则报错Unexpected token 'await' -
await后面可以跟非 Promise 值:此时会直接返回该值,相当于await Promise.resolve(值)。 -
async函数不会阻塞渲染主线程:await只会暂停当前async函数的执行(将后续代码逻辑封装为任务,等待当前Promise敲定后推入微队列),不会阻塞外部代码。
async function delay() {
console.log("开始等待");
await new Promise(resolve => setTimeout(resolve, 1000));
console.log("等待结束");
}
delay();
console.log("外部代码先执行"); // 会先输出这句,说明主线程没被阻塞
/*输出:
开始等待
外部代码先执行
(1秒后)等待结束
*/
并行处理的优化
并行处理异步操作:如果多个异步操作互不依赖,不要用 await 逐个等待(底层链式调用,会浪费时间),而应结合 Promise.all() 并行执行。
反例(低效)
async function bad() {
// 两个操作无依赖,但串行执行,总耗时 = t1 + t2
const res1 = await fetch("/api1");
const res2 = await fetch("/api2");
}
正例(高效)
async function good() {
// 并行执行,总耗时 = max(t1, t2)
const promise1 = fetch("/api1"); //先开启异步操作1
const promise2 = fetch("/api2"); //先开启异步操作2
const res1 = await promise1; //等待异步操作结果1
const res2 = await promise2; //等待异步操作结果2
}
上述两个代码本质是是否先启动所有异步操作再等待结果, 这是日常开发中优化异步性能的关键技巧。
当需要等待多个互不依赖的异步操作结果时,使用Promise.all()是更优雅的操作:
async function better() {
// 同时启动所有请求,等待全部完成后,按顺序返回结果数组
const [res1, res2] = await Promise.all([
fetch("/api1"),
fetch("/api2")
]);
console.log(res1); // 对应第一个 Promise 的结果
console.log(res2); // 对应第二个 Promise 的结果
}
Promise.all()需要其中所有的异步操作都兑现时才执行后续代码。
进阶:有时需要==「即使部分请求失败,也保留成功的结果」==,可以用 Promise.allSettled()(只要都敲定即可执行后续代码):
async function handlePartialFail() {
const results = await Promise.allSettled([
fetch("/api1"),
fetch("/api2")
]);
// 遍历结果,过滤出成功的请求
const successResults = results
.filter(result => result.status === "fulfilled")
.map(result => result.value);
}
更多推荐



所有评论(0)