Promise 是 JavaScript 中的对异步操作最佳的 API 之一。

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 一起执行。即使某些 promise 被 rejected,这些 rejections 也将会被忽略。 但是,如果输入数组中的所有 promises 都被拒绝,或者输入数组为空,那么 Promise.any() 会 rejected 包含输入的 promises 执行的 rejection 错误原因集合。 2. 使用指南 现在来深入介绍一下 Promise.any(), 在这之前,先来定义 2 个简单的函数。 函数 resolveTimeout(value, delay) 将返回一个在经过 delay 时间后有 resolve 的 promise 。 function resolveTimeout(value, delay) { return new Promise((resolve) => setTimeout(() => resolve(value), delay)); } 函数 rejectTimeout(reason, delay) 将返回一个在经过 delay 时间后有 reject 的 promise 。 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秒内 解析到数组 fruits 的 promise,因为解析fruits的 promise 先执行完成。 第二个是 2秒内 解析到数组 vegetables 的 promise,其值将被忽略。 2.2 一个 promise 被 rejected 将上面第一个 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' ] 上面的代码,第一个 promise 在 1秒后 被rejected,从执行的结果不难看出 Promise.any() 跳过了第一个被rejected的promise ,等待第二个 2秒后 执行完成的promise。 2.3 所有的 promises 被 rejected 下面来看下当所有的 promises 被 rejected 会出现什么结果,如下代码: 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] 从上面代码的执行结果来看,当所有输入promises 被 rejected 后, 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 实例, 那个输入的所有 promise 的 resolve 回调的结果是一个数组。 下面来看看 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 的顺序是很重要的——将按照这个顺序得到已实现的值。 但是如果至少有一个 promise 被 rejected ,那么 allPromise 会以同样的原因立即 rejected (不等待其他 promise 的执行)。 如果所有的 promise 被 rejected ,等待所有的promise 执行完成,但只会返回最先被rejected 的promise 的 reject 原因。 2. 使用指南 现在来深入介绍一下 Promise.all(), 在这之前,先来定义 2 个简单的函数。 函数 resolveTimeout(value, delay) 将返回一个在经过 delay 时间后有 resolve 的 promise 。 function resolveTimeout(value, delay) { return new Promise((resolve) => setTimeout(() => resolve(value), delay)); } 函数 rejectTimeout(reason, delay) 将返回一个在经过 delay 时间后有 reject 的 promise 。 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() 返回的 promise 的 resolve 数组是按照执行前 allPromise 的顺序组成其结果。 promise 数组的顺序直接影响结果的顺序,和 promise 执行完成的先后无关。 2.2 一个 promise 被 rejected 将上面数组 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 ,不难看出 allPromise 被 rejected 的原因是最先 rejected 的promise 。 Promise.all() 的这种行为被称为快速失败,如果 promise 数组中至少有一个 promise 被 rejected ,那么返回的 promise 也被拒绝。如果promise 数组中所有的都被 rejected ,那么返回的promise 被拒绝的原因是先rejected的那一个。 小结 Promise.all() 是并行执行异步操作并获取所有 resolve 值的最佳方法,非常适合需要同时获取异步操作结果来进行下一步运算的场合。 Promise.allSettled() 方法 Promise.allSettled(promises) ,返回一个在所有给定的 promise 都已经 fulfilled 或 rejected 后的 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 被拒绝 可以使用 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() 返回的一个 promise 的 resolve 状态数组是按照执行前 statusesPromise 的顺序组成其结果。 2.2 一个 promise 被 rejected 将上面第一个 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') }] 即使输入数组中的第二个 promise 被 rejected , statusesPromise 仍然可以成功解析状态数组。 2.3 所有 promises 被 rejected 将上面所有的 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 结果(履行或拒绝): 可以使用 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() 返回最快履行的 promise 的 resolve 结果。 2.2 一个 promise 被 rejected 将上面第一个 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 从上面的结果看,最先完成的 promise 被 rejected ,那么 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 所有 promises 被 rejected 将上面所有的 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 是最快被拒绝的 。