评论

收藏

[HarmonyOS] 关于 Promise 你需要知道的一切

移动开发 移动开发 发布于:2021-10-22 11:25 | 阅读数:615 | 评论:0

DSC0000.jpg

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 的值。
DSC0001.jpeg

可以使用 then 方法提取第一个 promise 的值:
anyPromise.then((firstValue) => {
firstValue; // 第一个 promise 完成后返回的值
});
也可以使用 async/await 语法:
const firstValue = await anyPromise;
console.log(firstValue); // 第一个 promise 完成后返回的值
Promise.any() 返回的 promise 与任何第一个执行的 promise 一起执行。即使某些 promise 被 rejected,这些 rejections 也将会被忽略。
DSC0002.jpeg

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

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 的顺序是很重要的——将按照这个顺序得到已实现的值。
DSC0004.jpeg

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

如果所有的 promise 被 rejected ,等待所有的promise 执行完成,但只会返回最先被rejected 的promise 的 reject 原因。
DSC0006.jpeg

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 被拒绝

DSC0007.jpeg

可以使用 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 结果(履行或拒绝):
DSC0008.jpeg

DSC0009.jpeg

可以使用 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 是最快被拒绝的 。
3. 使用场景
  3.1. 性能测试
在有异步操作的项目中,在对于网络或数据库请求进行性能进行优化的时候,可以使用 Promises 来测试其优化效果,通过使用 Promise.race() 来测试两种不同的方法的响应速度。
3.2 最佳选择
例如获取同一类型的数据有多个请求服务器,同时向多个服务器发送请求,只要其中一个完成工作,就将其数据呈现,达到选择最佳线路的效果。这是可以使用 Promise.race() 同时执行 promise 并在第一个成功后立即完成。
小结
Promise.race() 为第一个已解决和已拒绝的 promise 执行回调函数,而 Promise.any() 为第一个已履行的 promise 执行回调函数,如果没有履行的 promise 则拒绝一个特殊属性 AggregateError 。

关注下面的标签,发现更多相似文章