您现在的位置是:网站首页> 编程资料编程资料
Promise静态四兄弟实现示例详解_JavaScript_
2023-05-24
214人已围观
简介 Promise静态四兄弟实现示例详解_JavaScript_
前言
恰逢 Promise 也有四个很像的静态三兄弟(Promise.all、Promise.allSettled、Promise.race、Promise.any),它们接受的参数类型相同,但各自逻辑处理不同,它们具体会有什么区别那?别急,下面等小包慢慢道来。
在文章的开始,小包先给大家提出几个问题:
Promise.all与Promise.allSettled有啥区别啊?Promise.race的运行机制?Promise.any呐,两者有啥区别?- 四兄弟只能接受数组作为参数吗?
- 四兄弟方法我们应该如何优雅完美的实现?
Promise.all
Promise.all 在目前手写题中热度频度应该是 top5 级别的,所以我们要深刻掌握 Promise.all 方法。下面首先来简单回顾一下 all 方法。
基础学习
Promise.all 方法类似于一群兄弟们并肩前行,参数可以类比为一群兄弟,只有当兄弟全部快乐,all 老大才会收获快乐;只要有一个兄弟不快乐,老大就不会快乐。
Promise.all() 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
Promise.all 方法接受一个数组做参数,p1、p2、p3 都是 Promise 实例。如果不是 Promise 实例,则会先调用 Promise.resolve 方法将参数先转化为 Promise 实例,之后进行下一步处理。
返回值 p 的状态由 p1、p2、p3 决定,可以分成两种情况:
- 只有
p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。 - 只要
p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
// 模拟异步的promise const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(1); }, 1000); }); // 普通promise const p2 = Promise.resolve(2); // 常数值 const p3 = 3; // 失败的promise const p4 = Promise.reject("error"); // 异步失败的promise const p5 = new Promise((resolve, reject) => { setTimeout(() => { reject("TypeError"); }, 1000); }); // 1. promise全部成功 Promise.all([p1, p2, p3]) .then((data) => console.log(data)) // [1, 2, 3] .catch((error) => console.log(error)); // 2. 存在失败的promise Promise.all([p1, p2, p3, p4]) .then((data) => console.log(data)) .catch((error) => console.log(error)); // error // 3. 存在多个失败的promise Promise.all([p1, p2, p3, p4, p5]) .then((data) => console.log(data)) .catch((error) => console.log(error)); // error 从上面案例的输出中,我们可以得出下列结论:
p状态由参数执行结果决定,全部成功则返回成功,存有一个失败则失败- 参数为非
Promise实例,会通过Promise.resolve转化成Promise实例 - 成功后返回一个数组,数组内数据按照参数顺序排列
- 短路效应: 只会返回第一个失败信息
Iterator 接口参数
《ES6 入门教程》还指出: Promise.all 方法可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例
说实话,加粗部分小包是没能完全理解的,难道 Promise.all 使用 Iterator 类型时,要求迭代项都是 Promise 实例吗?我们以 String 类型为例,看 Promise.all 是否可以支持迭代项为非 Promise 实例。
// ['x', 'i', 'a', 'o', 'b', 'a', 'o'] Promise.all("xiaobao").then((data) => console.log(data)); 可见 Promise 对 Iterator 类型的处理与数组相同,如果参数不是 Promise 实例,会先调用 Promise.all 转化为 Promise 实例。
思路分析
Promise.all会返回一个新Promise对象
Promise.all = function (promises) { return new Promise((resolve, reject) => {}); }; - (亮点)
all方法参数可以是数组,同样也可以是Iterator类型,因此应该使用for of循环进行遍历。
Promise.all = function (promises) { return new Promise((resolve, reject) => { for (let p of promises) { } }); }; - 某些参数有可能未必是
Promise类型,因此参数使用前先通过Promise.resolve转换
Promise.all = function (promises) { return new Promise((resolve, reject) => { for (let p of promises) { // 保证所有的参数为 promise 实例,然后执行后续操作 Promise.resolve(p).then((data) => { //... }); } }); }; Iterator 类型我们是无法得知迭代深度,因此我们要维护一个 count 用来记录 promise 总数,同时维护 fulfilledCount 代表完成的 promise 数,当 count === fulfilledCount ,代表所有传入的 Promise 执行成功,返回数据。
Promise.all = function (promises) { let count = 0; // promise总数 let fulfilledCount = 0; // 完成的promise数 return new Promise((resolve, reject) => { for (let p of promises) { count++; // promise总数 + 1 Promise.resolve(p).then((data) => { fulfilledCount++; // 完成的promise数量+1 if (count === fulfilledCount) { // 代表最后一个promise完成了 resolve(); } }); } }); }; 有可能有的读者会好奇,为啥 count === fulfilledCount 可以判断所有的 promise 都完成了呐?
Promise.then 方法是 microTasks(微任务),当同步任务执行完毕后,Event Loop 才会去执行 microTasks。count++ 位于同步代码部分,因此在执行 promise.then 方法之前,已经成功的计算出 promise 的总数。
然后依次执行 promise.then 方法,fulfilledCount 增加,当 count === fulfilledCount 说明所有的 promise 都已经成功完成了。
返回数据的顺序应该是 all 方法中比较难处理的部分。
- 创建一个数组
result存储所有promise成功的数据 - 在
for of循环中,使用let变量定义i,其值等于当前的遍历索引 let定义的变量不会发生变量提升,因此我们直接令result[i]为promise成功数据,这样就可以实现按参数输入顺序输出结果
Promise.all = function (promises) { const result = []; // 存储promise成功数据 let count = 0; let fulfilledCount = 0; return new Promise((resolve, reject) => { for (let p of promises) { // i为遍历的第几个promise // 使用let避免形成闭包问题 let i = count; count++; // 保证所有的参数为 promise 实例,然后执行后续操作 Promise.resolve(p).then((data) => { fulfilledCount++; // 将第i个promise成功数据赋值给对应位置 result[i] = data; if (count === fulfilledCount) { // 代表最后一个promise完成了 // 返回result数组 resolve(result); } }); } }); }; 处理一下边界情况
- 某个
promise失败——直接调用reject即可 - 传入
promise数量为0——返回空数组(规范规定) - 代码执行过程抛出异常 —— 返回错误信息
// 多余代码省略 Promise.all = function (promises) { return new Promise((resolve, reject) => { // 3.捕获代码执行中的异常 try{ for (let p of promises) { Promise.resolve(p).then(data => {} .catch(reject); // 1.直接调用reject函数返回失败原因 }) } // 2.传入promise数量为0 if (count === 0) { resolve(result) } } catch(error) { reject(error) } }) } 源码实现
我们把上面的代码汇总一下,加上详细的注释,同时测试一下手写 Promise.all 是否成功。
Promise.all = function (promises) { const result = []; // 存储promise成功数据 let count = 0; // promise总数 let fulfilledCount = 0; //完成promise数量 return new Promise((resolve, reject) => { // 捕获代码执行中的异常 try { for (let p of promises) { // i为遍历的第几个promise // 使用let避免形成闭包问题 let i = count; count++; // promise总数 + 1 Promise.resolve(p) .then((data) => { fulfilledCount++; // 完成的promise数量+1 // 将第i个promise成功数据赋值给对应位置 result[i] = data; if (count === fulfilledCount) { // 代表最后一个promise完成了 // 返回result数组 resolve(result); } }) .catch(reject); // 传入promise数量为0 if (count === 0) { resolve(result); // 返回空数组 } } } catch (error) { reject(error); } }); }; 测试代码(使用案例中的测试代码,附加 Iterator 类型 Stirng):
// 1. promise全部成功 Promise.all([p1, p2, p3]) .then((data) => console.log(data)) // [1, 2, 3] .catch((error) => console.log(error)); // 2. 存在失败的promise Promise.all([p1, p2, p3, p4]) .then((data) => console.log(data)) .catch((error) => console.log(error)); // error // 3. 存在多个失败的promise Promise.all([p1, p2, p3, p4, p5]) .then((data) => console.log(data)) .catch((error) => console.log(error)); // error // 4. String 类型 Promise.all("zcxiaobao").then((data) => console.log(data)); // ['z', 'c', 'x', 'i', 'a', 'o', 'b', 'a', 'o'] Promise.allSettled
基础学习
不是每群兄弟们都会碰到好老大(all 方法),allSettled 方法他并不管兄弟们的死活,他只管兄弟们是否做了,而他的任务就是把所有兄弟的结果返回。
Promise.allSettled() 方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是 fulfilled 还是 rejec
相关内容
- JavaScript异步队列进行try catch时的问题解决_javascript技巧_
- JavaScript 的setTimeout与事件循环机制event-loop_javascript技巧_
- React组件设计过程之仿抖音订单组件_React_
- Node.js自定义对象事件的监听与发射_node.js_
- node.js express和koa中间件机制和错误处理机制_node.js_
- 三张图带你搞懂JavaScript的原型对象与原型链_基础知识_
- Three.js实现雪糕地球的使用示例详解_JavaScript_
- Node.js中Process.nextTick()和Process.setImmediate()的区别_node.js_
- JS正则表达式替换字符串replace()方法实例代码_javascript技巧_
- vue iview封装模态框的方法_vue.js_
