简言
什么是Promise?
Promise 是一个对象,它代表了一个异步操作的最终完成或者失败。它可以进行链式调用、错误捕捉等操作,轻松解决经典的回调地狱问题,可以像编写同步代码那样,从容、简洁、优雅地实现高复杂度的异步函数组合调用代码。
Promise
Promise 对象表示异步操作最终的完成(或失败)以及其结果值。
Promise描述
一个 Promise 是一个代理,它代表一个在创建 promise 时的不确定的值。 它使得异步方法可以像同步方法一样返回值:异步方法不会立即返回最终值,而是返回一个 promise,以便在将来的某个时间点提供该值。
一个Promise有以下几种状态之一:
- 待定(pending):初始状态,既没有被兑现,也没有被拒绝,类似于等待结果值状态。
- 已兑现(fulfilled):意味着操作成功完成,已知道结果值。
- 已拒绝(rejected):意味着操作失败状态。
一个待定的 Promise 最终状态可以是已兑现并返回一个值,或者是已拒绝并返回一个原因(错误)。当其中任意一种情况发生时,通过 Promise 的 then 方法串联的处理程序将被调用。如果绑定相应处理程序时 Promise 已经兑现或拒绝,这处理程序将被立即调用。
如果一个 Promise 已经被兑现或拒绝,即不再处于待定状态,那么则称之为已敲定(settled)。你还会听到使用已解决(resolved)这个术语来描述 Promise——这意味着该 Promise 已经敲定(settled),或为了匹配另一个 Promise 的最终状态而被“锁定(lock-in)”,进一步解决或拒绝它都没有影响。
mdn上的图,从左往右看:
Promise使用
promise使用比较方便,记住链式调用即可,它有多个方法供不同场景使用,大多方法返回的值是promise对象。
Promise构造函数
Promise() 构造函数创建 Promise 对象。
语法:
new Promise(executor)
-executor — 是一个函数,它接收两个函数作为参数:resolveFunc(抛出结果函数) 和 rejectFunc(抛出失败函数)。
当通过 new 关键字调用 Promise 构造函数时,它会返回一个 Promise 对象。当 resolveFunc 或者 rejectFunc 被调用时,该 Promise 对象就会变为已解决(resolved)
示例:
const pro = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("success");
}, 1000);
});
console.log(pro, Object.prototype.toString.call(pro));
pro.then(() => {
console.log(pro);
});
以下是典型的 Promise 流程概述:
-
在构造函数生成新的 Promise 对象时,它还会生成一对相应的 resolveFunc 和 rejectFunc 函数;它们与 Promise 对象“绑定”在一起。
-
executor 通常会封装某些提供基于回调的 API 的异步操作。回调函数(传给原始回调 API 的函数)在 executor 代码中定义,因此它可以访问 resolveFunc 和 rejectFunc。
-
executor 是同步调用的(在构造 Promise 时立即调用),并将 resolveFunc 和 rejectFunc 函数作为传入参数。
-
executor 中的代码有机会执行某些操作。异步任务的最终完成通过 resolveFunc 或 rejectFunc 引起的副作用与 Promise 实例进行通信。这个副作用让 Promise 对象变为“已解决”状态。
- 如果先调用 resolveFunc,则传入的值将解决。Promise 可能会保持待定状态(如果传入了另一个 thenable 对象),变为已兑现状态(在传入非 thenable 值的大多数情况下),或者变为已拒绝状态(在解析值无效的情况下)。
thenable对象就是实现Thenable接口(处理异步操作)的对象,Promise对象也是thenable对象。
- 如果先调用 rejectFunc,则 Promise 立即变为已拒绝状态。
- 一旦 resolveFunc 或 rejectFunc 中的一个被调用,Promise 将保持解决状态。只有第一次调用 resolveFunc 或 rejectFunc 会影响 Promise 的最终状态,随后对任一函数的调用都不能更改兑现值或拒绝原因,也不能将其最终状态从“已兑现”转换为“已拒绝”或相反。
- 如果 executor 抛出错误,则 Promise 被拒绝。但是,如果 resolveFunc 或 rejectFunc 中的一个已经被调用(因此 Promise 已经被解决),则忽略该错误。
- 解决 Promise 不一定会导致 Promise 变为已兑现或已拒绝(即已敲定)。Promise 可能仍处于待定状态,因为它可能是用另一个 thenable 对象解决的,但它的最终状态将与已解决的 thenable 对象一致。
-
一旦 Promise 敲定,它会(异步地)调用任何通过 then()、catch() 或 finally() 关联的进一步处理程序。最终的兑现值或拒绝原因在调用时作为输入参数传给兑现和拒绝处理程序
console.log(
new Promise((resolve) => {
resolve(new Error("error"));
})
);
console.log(
new Promise((resolve, reject) => {
reject("reject状态");
})
);
Promise链式调用(then函数)
Promise 实例的 then() 方法最多接受两个参数:用于 Promise 兑现和拒绝情况的回调函数。它立即返回一个等效的 Promise 对象,允许你链接到其他 Promise 方法,从而实现链式调用。
then() 函数会返回一个和原来不同的新的 Promise:
const promise = doSomething();
const promise2 = promise.then(successCallback, failureCallback);
promise2 不仅表示 doSomething() 函数的完成,也代表了你传入的 successCallback 或者 failureCallback 的完成,这两个函数也可以返回一个 Promise 对象,从而形成另一个异步操作,这样的话,在 promise2 上新增的回调函数会排在这个 Promise 对象的后面。
就像这样,每一个 Promise 都代表了链中另一个异步过程的完成。
then 的参数是可选的,catch(failureCallback) 等同于 then(null, failureCallback)
示例:
doSomething()
.then((result) => doSomethingElse(result))
.then((newResult) => doThirdThing(newResult))
.then((finalResult) => {
console.log(`得到最终结果:${finalResult}`);
})
.catch(failureCallback);
注意:一定要有返回值,否则,回调将无法获取上一个 Promise 的结果。
Catch 的后续链式操作
有可能会在一个回调失败之后继续使用链式操作,即,使用一个 catch,这对于在链式操作中抛出一个失败之后,再次进行新的操作会很有用(上面提示了,catch相当于拒绝状态的then)。
new Promise((resolve) => {
resolve(123);
})
.then((res) => {
console.log(res, "then");
// 抛错,触发catch
throw new Error(res);
})
.catch((res) => {
console.log(res, "catch");
return "catch返回结果";
})
.then((err) => {
console.log(err, "catch后的then");
});
Promise错误捕捉(catch)
Promise 实例的 catch() 方法用于注册一个在 promise 被拒绝时调用的函数。它会立即返回一个等效的 Promise 对象,这可以允许你链式调用其他 promise 的方法。此方法是 Promise.prototype.then(undefined, onRejected) 的一种简写形式。
new Promise((resolve, reject) => {
reject("错误啦");
}).catch((res) => {
console.log(res, "catch");
});
new Promise((resolve, reject) => {
reject("错误啦2");
});
注意点:
- 捕获不到异步函数内部抛出的错误。
- 在调用 resolve 之后抛出的错误会被忽略。
- 如果 Promise 已兑现,catch() 不会被调用,例如Promise.resolve()返回的promise对象。
finally函数
**Promise 实例的 finally() 方法用于注册一个在 promise 敲定(兑现或拒绝)时调用的函数。**它会立即返回一个等效的 Promise 对象,这可以允许你链式调用其他 promise 方法。
如果你想在 promise 敲定时进行一些处理或者清理,无论其结果如何,那么 finally() 方法会很有用。
finally() 方法类似于调用 then(onFinally, onFinally)。然而,有几个不同之处:
- 创建内联函数时,你可以只将其传入一次,而不是强制声明两次或为其创建变量。
- onFinally 回调函数不接收任何参数。这种情况恰好适用于你不关心拒绝原因或兑现值的情况,因此无需提供它。
- finally() 调用通常是透明的,不会更改原始 promise 的状态。例如 Promise.resolve(2).finally(() => 77) 返回一个最终兑现为值 2 的 promise。
在 finally 回调函数中强制抛出拒绝结果或错误),会导致promise状态变成拒绝状态。
console.log(
new Promise((resolve, reject) => {
reject("错误啦2");
}).finally(() => {
console.log("finally");
return Promise.reject("finally1");
})
);
console.log(
new Promise((resolve, reject) => {
resolve("success");
}).finally(() => {
console.log("finally");
return Promise.reject("finally2");
})
);
Promise.resolve()
Promise.resolve() 静态方法将给定的值转换为一个 Promise。如果该值本身就是一个 Promise,那么该 Promise 将被返回;如果该值是一个 thenable 对象,Promise.resolve() 将调用其 then() 方法及其两个回调函数;否则,返回的 Promise 将会以该值兑现。
该函数将嵌套的类 Promise 对象(例如,一个将被兑现为另一个 Promise 对象的 Promise 对象)展平,转化为单个 Promise 对象,其兑现值为一个非 thenable 值。
Promise.resolve() 返回一个 Promise 对象,其最终状态取决于另一个 Promise 对象、thenable 对象或其他值。
Promise.resolve("成功").then(
(value) => {
console.log(value); // "成功"
},
(reason) => {
// 不会被调用
},
);
Promise.resolve() 方法会重用已存在的 Promise 实例。如果它正在解决一个原生的 Promise,它将返回同一 Promise 实例,而不会创建一个封装对象。
const original = Promise.resolve(33);
const cast = Promise.resolve(original);
cast.then((value) => {
console.log(`值:${value}`);
});
console.log(`original === cast ? ${original === cast}`);
// 按顺序打印:
// original === cast ? true
// 值:33
Promise.reject()
Promise.reject() 静态方法返回一个已拒绝(rejected)的 Promise 对象,拒绝原因为给定的参数。
Promise.reject(reason)
Promise.reject 静态方法返回一个被拒绝的 Promise 对象。通过使用 Error 的实例获取错误原因 reason 对调试和选择性错误捕捉很有帮助。
const p = Promise.resolve(1);
const rejected = Promise.reject(p);
console.log(rejected === p); // false
rejected.catch((v) => {
console.log(v === p); // true
});
与 Promise.resolve 不同,Promise.reject 方法不会重用已存在的 Promise 实例。
Promise组合使用方法
Promise有多个静态方法用于快速处理promise可迭代对象。
-
Promise.all() — Promise.all() 静态方法接受一个 Promise 可迭代对象作为输入,并返回一个 Promise。当所有输入的 Promise 都被兑现时,返回的 Promise 也将被兑现(即使传入的是一个空的可迭代对象),并返回一个包含所有兑现值的数组。如果输入的任何 Promise 被拒绝,则返回的 Promise 将被拒绝,并带有第一个被拒绝的原因。理想情况下是返回promise结果数组,Promise.all() 方法会在任何一个输入的 Promise 被拒绝时立即拒绝。
Promise.all() 方法是 promise 并发方法之一。它可用于聚合多个 Promise 的结果。通常在有多个相关的异步任务并且整个代码依赖于这些任务成功完成时使用,我们希望在代码执行继续之前完成所有任务。
-
Promise.allSettled() — Promise.allSettled() 静态方法将一个 Promise 可迭代对象作为输入,并返回一个单独的 Promise。当所有输入的 Promise 都已敲定时(包括传入空的可迭代对象时),返回的 Promise 将被兑现,并带有描述每个 Promise 结果的对象数组。也是返回结果数组,不过即使其中有失败也不会阻止其他promise
Promise.allSettled() 方法是 promise 并发方法之一。在你有多个不依赖于彼此成功完成的异步任务时,或者你总是想知道每个 promise 的结果时,使用 Promise.allSettled() 。
-
Promise.any() — Promise.any() 静态方法将一个 Promise 可迭代对象作为输入,并返回一个 Promise。当输入的任何一个 Promise 兑现时,这个返回的 Promise 将会兑现,并返回第一个兑现的值。当所有输入 Promise 都被拒绝(包括传递了空的可迭代对象)时,它会以一个包含拒绝原因数组的 AggregateError 拒绝。只要有一个欧容米色成功就返回第一个成功的promise值,都拒绝则返回拒绝原因数组
Promise.any() 方法是 Promise 并发方法之一。该方法对于返回第一个兑现的 Promise 非常有用。一旦有一个 Promise 兑现,它就会立即返回,因此不会等待其他 Promise 完成。
-
Promise.race() 静态方法接受一个 promise 可迭代对象作为输入,并返回一个 Promise。这个返回的 promise 会随着第一个 promise 的敲定而敲定。
Promise.race() 方法是 Promise 并发方法之一。当你想要第一个异步任务完成时,但不关心它的最终状态(即它既可以成功也可以失败)时,它就非常有用。
四个组合方法两两相似,all和allSettled,any和race,建议对比记忆。
Promise.all 的快速失败行为.
Promise.all 在任意一个传入的 promise 失败时返回失败。例如,如果你传入四个超时后解决的 promise 和一个立即拒绝的 promise,那么 Promise.all 将立即被拒绝。
const p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve("一"), 1000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => resolve("二"), 2000);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => resolve("三"), 3000);
});
const p4 = new Promise((resolve, reject) => {
setTimeout(() => resolve("四"), 4000);
});
const p5 = new Promise((resolve, reject) => {
reject(new Error("拒绝"));
});
// 使用 .catch:
Promise.all([p1, p2, p3, p4, p5])
.then((values) => {
console.log(values);
})
.catch((error) => {
console.error(error.message);
});
// 打印:
// "拒绝"
使用 Promise.allSettled():
Promise.allSettled([
Promise.resolve(33),
new Promise((resolve) => setTimeout(() => resolve(66), 0)),
99,
Promise.reject(new Error("一个错误")),
]).then((values) => console.log(values));
// [
// { status: 'fulfilled', value: 33 },
// { status: 'fulfilled', value: 66 },
// { status: 'fulfilled', value: 99 },
// { status: 'rejected', reason: Error: 一个错误 }
// ]
使用 Promise.any()显示第一张已加载的图片:
async function fetchAndDecode(url, description) {
const res = await fetch(url);
if (!res.ok) {
throw new Error(`HTTP 错误!状态码:${res.status}`);
}
const data = await res.blob();
return [data, description];
}
const coffee = fetchAndDecode("coffee.jpg", "Coffee");
const tea = fetchAndDecode("tea.jpg", "Tea");
Promise.any([coffee, tea])
.then(([blob, description]) => {
const objectURL = URL.createObjectURL(blob);
const image = document.createElement("img");
image.src = objectURL;
image.alt = description;
document.body.appendChild(image);
})
.catch((e) => {
console.error(e);
});
使用 Promise.race() 实现请求超时:
const data = Promise.race([
fetch("/api"),
new Promise((resolve, reject) => {
// 5 秒后拒绝
setTimeout(() => reject(new Error("请求超时")), 5000);
}),
])
.then((res) => res.json())
.catch((err) => displayError(err));
结语
Promise是微任务的一种。
结束了。