写点什么

关于 Promise 你需要知道的一切

用户头像
devpoint
关注
发布于: 2021 年 09 月 30 日
关于Promise你需要知道的一切

Promise 是 JavaScript 中的对异步操作最佳的 API 之一。作为 JavaScript 开发人员,需要熟练掌握 Promise 相关的知识。本文就来总结一下这些知识点,先来回顾 JavaScript 过去异步操作的处理方式?然后详细介绍 Promises 对象及相关的方法。


现在先来了解一下 JavaScript 异步概念。

异步回顾

什么是异步?如果在函数返回的时候,调用者还不能获取到预期结果,而是将来通过一定的方式得到(例如回调函数),这函数就是异步。

异步回调

异步回调,就是常见的 callback ,这是过去在 JavaScript 中处理异步操作的常见方式,如 AJAX ,发起一个 HTTP 请求,具体服务器什么时候返回响应数据取决于客户的环境,存在很多不确定因素,这时采用回调函数可以在返回响应数据的时候触发回调函数。


这种方式当有多个请求的情况,后续请求需要等待前面请求的响应数据的时候,就会出现常见的回调地狱。


const asyncMessage = function (message, callback) {    setTimeout(function () {        console.log(message);        callback();    }, 1000);};
asyncMessage("title", function () { asyncMessage("cate", function () { asyncMessage("content", function () { console.log("detail"); }); });});
复制代码


出现回调地狱将会给项目开发带来很多问题:


  • 会导致逻辑混乱,耦合性高,改动一处就会导致全部变动,嵌套多时,BUG 问题难以发现。

  • 不能使用 try...catch 来捕获异常。

  • 不能使用 return 返回真实数据


为了避免回调地狱的出现,后来就有了 Promise 对象。

Promise 的工作方式

promise 对象是一个可以从异步函数同步返回的对象,它将处于 3 种可能的状态之一:


fulfilled 已兑现: onFulfilled() 将被调用,即操作完成(例如,resolve() 被调用)rejected 已拒绝: onRejected() 将被调用,即操作失败(例如,reject() 被调用)pending 待定:初始状态,既没有被兑现,也没有被拒绝


如果一个 promise 没有挂起(它已被解决或拒绝),它就会被解决。有时使用已解决和已解决表示同一件事:不是 pending


promise 一旦确定,就不能再重新确定,再次调用 resolve() reject() 将没有效果。一个已确定的 promise 的具有不可变性。


对于 promise 的状态监控可以使用承诺链,即在 fulfilled 已兑现的状态可以使用 then 方法可以获取已兑现的结果,在rejected 已拒绝状态使用 catch 方法获取拒绝的原因。


const myPromise = new Promise(myExecutorFunc)    .then(handleFulfilledA)    .then(handleFulfilledB)    .then(handleFulfilledC)    .catch(handleRejectedAny);
复制代码


看起来比 callback 的方式优雅一点,对于需要发起多次 HTTP 请求才能完整呈现的需求,代码如下:


const getPost = () => fetch("https://jsonplaceholder.typicode.com/posts/1");const getAuthor = (id) =>    fetch("https://jsonplaceholder.typicode.com/users/" + id);const getComment = (id) =>    fetch("https://jsonplaceholder.typicode.com/users/" + id);
getPost() // #1.fetch post .then((postResponse) => postResponse.json()) // #2. get & return post json .then((postResponse) => getAuthor(postResponse.id) // #3. fetch author .then((authorResponse) => authorResponse .json() // #4 get & return author json .then((authorResponse) => getComment(postResponse.id) // #5 fetch comment .then((commentResponse) => commentResponse.json()) // #6 get & return comment json .then((commentResponse) => { // #7 time to combine all results return { postResponse, authorResponse, commentResponse, }; // #8 combine & return all reponses }) ) ) .then((results) => { // #9 read all responses console.log(results.postResponse); console.log(results.authorResponse); console.log(results.commentResponse); }) ) .catch((error) => console.log(error)); // # 10 error handling
复制代码


上面代码是否有种似曾相识的感觉,原本是为了解决回调地狱,但似乎理想跟现实还是有差距。


于是 ES2021 为 Promise 对象增加新的特征,其中包括:Promise.any()Promise.all()Promise.allSettled()Promise.race()

Promise.any()

Promise.any(promises) 能够并行运行 promise,并解析为 promises 列表中第一个成功解析的 promise 的值。需要注意的是 Promise.any() 方法依然是实验性的,尚未被所有的浏览器完全支持。


下面来看看 Promise.any() 是如何工作的。

1.工作原理

Promise.any() 可用于以并行和竞争方式执行独立的异步操作,以获取任何第一个完成的 promise 的值。


该函数接受一个 promise 数组(通常为一个可迭代对象)作为参数,如下:


const anyPromise = Promise.any(promises);
复制代码


当输入 promises 中的第一个 promise 被执行完成时,anyPromise 会立即解析为该 promise 的值。



可以使用 then 方法提取第一个 promise 的值:


anyPromise.then((firstValue) => {    firstValue; // 第一个 promise 完成后返回的值});
复制代码


也可以使用 async/await 语法:


const firstValue = await anyPromise;console.log(firstValue); // 第一个 promise 完成后返回的值
复制代码


Promise.any() 返回的 promise 与任何第一个执行的 promise 一起执行。即使某些 promiserejected,这些 rejections 也将会被忽略。



但是,如果输入数组中的所有 promises 都被拒绝,或者输入数组为空,那么 Promise.any()rejected 包含输入的 promises 执行的 rejection 错误原因集合。


2. 使用指南

现在来深入介绍一下 Promise.any(), 在这之前,先来定义 2 个简单的函数。


函数 resolveTimeout(value, delay) 将返回一个在经过 delay 时间后有 resolvepromise


function resolveTimeout(value, delay) {    return new Promise((resolve) => setTimeout(() => resolve(value), delay));}
复制代码


函数 rejectTimeout(reason, delay) 将返回一个在经过 delay 时间后有 rejectpromise


function rejectTimeout(reason, delay) {    return new Promise((r, reject) => setTimeout(() => reject(reason), delay));}
复制代码


接下来使用上面定义的 2 个辅助函数来试试 Promise.any()

2.1 完成所有 promises

下面尝试运行第一个解析列表:


function resolveTimeout(value, delay) {    return new Promise((resolve) => setTimeout(() => resolve(value), delay));}function rejectTimeout(reason, delay) {    return new Promise((r, reject) => setTimeout(() => reject(reason), delay));}const fruits = ["potatoes", "tomatoes"];const vegetables = ["oranges", "apples"];const promise = Promise.any([    resolveTimeout(fruits, 1000),    resolveTimeout(vegetables, 2000),]);
// 等待...const list = async () => { const result = await promise; console.log(result);};
// 1 秒之后list(); // ['potatoes', 'tomatoes']
复制代码


promise .any([…]) 返回一个在 1秒内 解析到数组 fruitspromise,因为解析 fruits 的 promise 先执行完成。


第二个是 2秒内 解析到数组 vegetablespromise,其值将被忽略。

2.2 一个 promiserejected

将上面第一个 promise 出现异常被 rejected ,如下代码:


function resolveTimeout(value, delay) {    return new Promise((resolve) => setTimeout(() => resolve(value), delay));}function rejectTimeout(reason, delay) {    return new Promise((r, reject) => setTimeout(() => reject(reason), delay));}const vegetables = ["oranges", "apples"];
const promise = Promise.any([ rejectTimeout(new Error("fruits is empty"), 1000), resolveTimeout(vegetables, 2000),]);
// 等待...const list = async () => { const result = await promise; console.log(result);};
// 2 秒之后list(); // [ 'oranges', 'apples' ]
复制代码


上面的代码,第一个 promise1秒后rejected,从执行的结果不难看出 Promise.any() 跳过了第一个被rejectedpromise ,等待第二个 2秒后 执行完成的promise

2.3 所有的 promisesrejected

下面来看下当所有的 promisesrejected 会出现什么结果,如下代码:


function rejectTimeout(reason, delay) {    return new Promise((r, reject) => setTimeout(() => reject(reason), delay));}const promise = Promise.any([    rejectTimeout(new Error("fruits is empty"), 1000),    rejectTimeout(new Error("vegetables is empty"), 2000),]);
// 等待...const list = async () => { try { const result = await promise; console.log(result); } catch (aggregateError) { console.log(aggregateError); console.log(aggregateError.errors); }};
list(); // [AggregateError: All promises were rejected]
复制代码


从上面代码的执行结果来看,当所有输入promisesrejected 后, Promise.any([...]) 将返回一种特殊的错误 AggregateError 而被 rejected ,而详细的 rejected 原因在属性 aggregateError.errors 中 。

小结

Promise.any() 可用于以竞争方式并行执行独立的异步操作,以获取任何第一个成功执行完成的 promise 的值。如果 Promise.any() 的所有输入 promise 都被rejected 后,那么辅助函数返回的 promise 也会以错误集合的方式拒绝,该错误在一个特殊属性 AggregateError 中包含输入 promise 的拒绝原因:aggregateError.errors

Promise.all()

方法 Promise.all(promises) ,能够一次并行处理多个 promise,并且只返回一个 promise 实例, 那个输入的所有 promiseresolve 回调的结果是一个数组。


下面来看看 Promise.all() 是如何工作的。

1.工作原理

Promise.all() 是一个内置的辅助函数,接受一组 promise(或者一个可迭代的对象),并返回一个promise


const allPromise = Promise.all([promise1, promise2, ...]);
复制代码


可以使用 then 方法提取第一个 promise 的值:


allPromise.then((values) => {    values; // [valueOfPromise1, valueOfPromise2, ...]});
复制代码


也可以使用 async/await 语法:


const values = await allPromise;console.log(values); // [valueOfPromise1, valueOfPromise2, ...]
复制代码


Promise.all() 返回的 promise 被解析或拒绝的方式。


如果 allPromise 都被成功解析,那么 allPromise 将使用一个包含各个 promise 已执行完成后的值的数组作为结果。数组中 promise 的顺序是很重要的——将按照这个顺序得到已实现的值。



但是如果至少有一个 promiserejected ,那么 allPromise 会以同样的原因立即 rejected (不等待其他 promise 的执行)。



如果所有的 promiserejected ,等待所有的promise 执行完成,但只会返回最先被rejectedpromisereject 原因。


2. 使用指南

现在来深入介绍一下 Promise.all(), 在这之前,先来定义 2 个简单的函数。


函数 resolveTimeout(value, delay) 将返回一个在经过 delay 时间后有 resolvepromise


function resolveTimeout(value, delay) {    return new Promise((resolve) => setTimeout(() => resolve(value), delay));}
复制代码


函数 rejectTimeout(reason, delay) 将返回一个在经过 delay 时间后有 rejectpromise


function rejectTimeout(reason, delay) {    return new Promise((r, reject) => setTimeout(() => reject(reason), delay));}
复制代码


接下来使用上面定义的 2 个辅助函数来试试 Promise.all()

2.1 完成所有 promises

下面定义了一个 promise 数组 allPromise ,所有的 promise 都能够成功的 resolve 值,如下:


function resolveTimeout(value, delay) {    return new Promise((resolve) => setTimeout(() => resolve(value), delay));}const fruits = ["potatoes", "tomatoes"];const vegetables = ["oranges", "apples"];
const allPromise = [ resolveTimeout(fruits, 2000), resolveTimeout(vegetables, 1000),];const promise = Promise.all(allPromise);
// 等待... 2秒后const list = async () => { try { const result = await promise; console.log(result); } catch (error) { console.log(error.errors); }};
list(); // [ [ 'potatoes', 'tomatoes' ], [ 'oranges', 'apples' ] ]
复制代码


从上面执行的结果来看 Promise.all() 返回的 promiseresolve 数组是按照执行前 allPromise 的顺序组成其结果。


promise 数组的顺序直接影响结果的顺序,和 promise 执行完成的先后无关。

2.2 一个 promiserejected

将上面数组 allPromise 的第一个 promise 出现异常被 rejected ,如下代码:


const promise = Promise.all([    rejectTimeout(new Error("fruits is empty"), 5000),    resolveTimeout(vegetables, 1000),]);
// 等待...const list = async () => { try { const result = await promise; console.log(result); } catch (error) { console.log(error); }};
list(); // Error: fruits is empty
复制代码


然而,在经过 5秒 之后,第一个 promise 由于异常被 rejected ,使得 allPromise 也被 rejected ,并返回跟第一个 promise 一样的错误信息:Error: fruits is empty ,即使在 1秒 后就完成的第二个 promise 的值也不被采纳。


接下来将数组 allPromise 的所有 promise 都抛出异常被 rejected ,通过定时器将 rejected 的顺序做个调整,如下:


const promise = Promise.all([    rejectTimeout(new Error("fruits is empty"), 5000),    rejectTimeout(new Error("vegetables is empty"), 1000),]);
// 等待...const list = async () => { try { const result = await promise; console.log(result); } catch (error) { console.log(error); }};
复制代码


经过 5秒 之后完成执行,而结果显示为 Error: vegetables is empty ,不难看出 allPromiserejected 的原因是最先 rejectedpromise


Promise.all() 的这种行为被称为快速失败,如果 promise 数组中至少有一个 promiserejected ,那么返回的 promise 也被拒绝。如果promise 数组中所有的都被 rejected ,那么返回的promise 被拒绝的原因是先rejected的那一个。

小结

Promise.all() 是并行执行异步操作并获取所有 resolve 值的最佳方法,非常适合需要同时获取异步操作结果来进行下一步运算的场合。

Promise.allSettled()

方法 Promise.allSettled(promises) ,返回一个在所有给定的 promise 都已经 fulfilledrejected 后的 promise ,并带有一个对象数组,每个对象表示对应的promise 结果。


下面来看看 Promise.allSettled() 是如何工作的。

1.工作原理

Promise.allSettled() 可用于并行执行独立的异步操作,并收集这些异步操作的结果。


函数接受一个 promise 数组(或通常是一个可迭代的)作为参数,如下:


const statusesPromise = Promise.allSettled(promises);
复制代码


当所有输入 promises 都被履行或拒绝时,statusesPromise 会解析为一个具有其状态的数组:


  • { status: 'fulfilled', value:value } : 如果相应的 promise 已经履行

  • { status: 'rejected', reason: reason } :如果相应的 promise 被拒绝


promise.allSettled


可以使用 then 方法提取所有 promises 的状态:


statusesPromise.then((statuses) => {    statuses; // [{ status: '...', value: '...' }, ...]});
复制代码


也可以使用 async/await 语法:


const statuses = await statusesPromise;
statuses; // [{ status: '...', value: '...' }, ...]
复制代码


Promise.allSettled() 返回的承诺总是以一系列状态实现,无论是否有一些(或者全部)输入承诺被拒绝。


Promise.allSettled()Promise.all() 的最大不同:Promise.allSettled() 永远不会被 rejected

2. 使用指南

现在来深入介绍 Promise.allSettled() 的使用之前, 还是先来定义 2 个简单的函数。


function resolveTimeout(value, delay) {    return new Promise((resolve) => setTimeout(() => resolve(value), delay));}function rejectTimeout(reason, delay) {    return new Promise((r, reject) => setTimeout(() => reject(reason), delay));}
复制代码


接下来使用上面定义的 2 个辅助函数来试试 Promise.allSettled()

2.1 完成所有 promises

下面定义了一个 promise 数组 statusesPromise ,所有的 promise 都能够成功的 resolve 值,如下:


const fruits = ["potatoes", "tomatoes"];const vegetables = ["oranges", "apples"];
const statusesPromise = Promise.allSettled([ resolveTimeout(fruits, 2000), resolveTimeout(vegetables, 1000),]);
// 等待 2 秒 ...const list = async () => { try { const statuses = await statusesPromise; console.log(statuses); } catch (error) { console.log(error); }};
list(); // [{ status: 'fulfilled', value: [ 'potatoes', 'tomatoes' ] },{ status: 'fulfilled', value: [ 'oranges', 'apples' ] }]
复制代码


从上面执行的结果来看 Promise.allSettled() 返回的一个 promiseresolve 状态数组是按照执行前 statusesPromise 的顺序组成其结果。

2.2 一个 promiserejected

将上面第一个 promise 出现异常被 rejected ,如下代码:


const fruits = ["potatoes", "tomatoes"];
const statusesPromise = Promise.allSettled([ resolveTimeout(fruits, 2000), rejectTimeout(new Error("Vegetables is empty"), 1000),]);
// 等待 2 秒 ...const list = async () => { try { const statuses = await statusesPromise; console.log(statuses); } catch (error) { console.log(error); }};
list(); // // [{ status: 'fulfilled', value: ['potatoes', 'tomatoes'] },{ status: 'rejected', reason: Error('Vegetables is empty') }]
复制代码


即使输入数组中的第二个 promiserejectedstatusesPromise 仍然可以成功解析状态数组。

2.3 所有 promisesrejected

将上面所有的 promises 出现异常被 rejected ,如下代码:


const statusesPromise = Promise.allSettled([    rejectTimeout(new Error("Fruits is empty"), 2000),    rejectTimeout(new Error("Vegetables is empty"), 1000),]);
// 等待 2 秒 ...const list = async () => { try { const statuses = await statusesPromise; console.log(statuses); } catch (error) { console.log(error); }};
list(); // // [{ status: 'rejected', reason: Error('Fruits is empty') },{ status: 'rejected', reason: Error('Vegetables is empty') }]
复制代码

小结

当需要执行并行和独立的异步操作并收集所有结果时,Promise.allSettled() 就是不错的选择,即使一些异步操作可能失败。

Promise.race()

方法 Promise.race(promises) ,顾名思义就是赛跑的意思,Promise.race([p1, p2, p3]) 里面 promise 数组那个执行完成得快就获取那个的结果,不管结果本身是成功履行状态还是失败拒绝状态,只输出最快的 promise


下面来看看 Promise.race() 是如何工作的。

1.工作原理

Promise.race() 返回一个 promise,一旦迭代器中的某个 promise 履行或拒绝,返回的 promise 就会履行或拒绝。


函数接受一个 promise 数组(或通常是一个可迭代的)作为参数,如下:


const racePromise = Promise.race(promises);
复制代码


当所有输入 promises 中有一个 promise 快速被履行或拒绝时,racePromise 就会解析快速完成的 promise 结果(履行或拒绝):


Promise.race()


Promise.race()


可以使用 then 方法提取 racePromise 的结果:


racePromise.then((fastValue) => {    fastValue // 快速完成的 promise});
复制代码


也可以使用 async/await 语法:


const fastPromise = await racePromise;
fastPromise; // 快速完成的 promise
复制代码


Promise.race() 返回的承诺和最先完成的承诺信息一致。


Promise.race()Promise.any() 的不同:Promise.race() 承诺列表中寻找第一个履行或拒绝的承诺;Promise.any() 是从承诺列表中查找第一个履行的承诺。

2. 使用指南

现在来深入介绍 Promise.race() 的使用之前,同样先来定义 2 个简单的函数。


function resolveTimeout(value, delay) {    return new Promise((resolve) => setTimeout(() => resolve(value), delay));}function rejectTimeout(reason, delay) {    return new Promise((r, reject) => setTimeout(() => reject(reason), delay));}
复制代码


接下来使用上面定义的 2 个辅助函数来试试 Promise.race()

2.1 完成所有 promises

下面定义了一个 promise 数组 racePromise ,所有的 promise 都能够成功的 resolve 值,如下:


const fruits = ["potatoes", "tomatoes"];const vegetables = ["oranges", "apples"];
const racePromise = Promise.race([ resolveTimeout(fruits, 5000), resolveTimeout(vegetables, 1000),]);
// 等待 1 秒 ...const list = async () => { try { const fastPromise = await racePromise; console.log(fastPromise); } catch (error) { console.log(error); }};
list(); // [ 'oranges', 'apples' ]
复制代码


从上面执行的结果来看 Promise.race() 返回最快履行的 promiseresolve 结果。

2.2 一个 promiserejected

将上面第一个 promise 出现异常被 rejected ,如下代码:


const fruits = ["potatoes", "tomatoes"];
const racePromise = Promise.race([ resolveTimeout(fruits, 2000), rejectTimeout(new Error("Vegetables is empty"), 1000),]);
// 等待 1 秒 ...const list = async () => { try { const fastPromise = await racePromise; console.log(fastPromise); } catch (error) { console.log(error); }};
list(); // Error: Vegetables is empty
复制代码


从上面的结果看,最先完成的 promiserejected ,那么 fastPromise 返回的 promise 也是被 rejected


下面将rejected 的承诺时间延长到 5 秒,如下:


const fruits = ["potatoes", "tomatoes"];
const racePromise = Promise.race([ resolveTimeout(fruits, 2000), rejectTimeout(new Error("Vegetables is empty"), 5000),]);
// 等待 2 秒 ...const list = async () => { try { const fastPromise = await racePromise; console.log(fastPromise); } catch (error) { console.log(error); }};
list(); // [ 'potatoes', 'tomatoes' ]
复制代码


从上面执行结果看到,最快完成的 promise 履行了 resolve ,那么 fastPromise 返回的 promise 也是履行了 resolve

2.3 所有 promisesrejected

将上面所有的 promises 出现异常被 rejected ,如下代码:


const racePromise = Promise.race([    rejectTimeout(new Error("Fruits is empty"), 2000),    rejectTimeout(new Error("Vegetables is empty"), 1000),]);
// 等待 1 秒 ...const list = async () => { try { const fastPromise = await racePromise; console.log(fastPromise); } catch (error) { console.log(error); }};
list(); // Error: Vegetables is empty
复制代码


从结果来看,虽然两个承诺都被拒绝了,fastPromise 返回的 promise 是最快被拒绝的 。

3. 使用场景

3.1. 性能测试

在有异步操作的项目中,在对于网络或数据库请求进行性能进行优化的时候,可以使用 Promises 来测试其优化效果,通过使用 Promise.race() 来测试两种不同的方法的响应速度。

3.2 最佳选择

例如获取同一类型的数据有多个请求服务器,同时向多个服务器发送请求,只要其中一个完成工作,就将其数据呈现,达到选择最佳线路的效果。这是可以使用 Promise.race() 同时执行 promise 并在第一个成功后立即完成。

小结

Promise.race() 为第一个已解决和已拒绝的 promise 执行回调函数,而 Promise.any() 为第一个已履行的 promise 执行回调函数,如果没有履行的 promise 则拒绝一个特殊属性 AggregateError



发布于: 2021 年 09 月 30 日阅读数: 20
用户头像

devpoint

关注

细节的追求者 2011.11.12 加入

专注前端开发,用技术创造价值!

评论

发布
暂无评论
关于Promise你需要知道的一切