面试官: 你会手写实现PromiseA+么? 小白的我: 这......, 要不我试试(系列二

215 阅读7分钟

问题/需求:

  • 话接上回, 面试官已经让我们实现了基本的Promise, 其中包括promise中的回调resolvereject, 还有处理完状态返回的.then方法, 那么下面,我们将继续实现Promise中处理多个异步请求/处理完成后, 再一次输出的方法all

解决/处理步骤

分析过程:

  • 首先老样子先分析一下all方法中, 需要传递的参数, 其实all方法的参数就是一个数组, 这个数组中按理说, 需要存储的都是promise类型的数据, 但是(都知道, 这世间不是所有的情况都是理想状态, 所以, 需要进行处理的, 但是, 首先我们还是按照假如都按照规范着,数组中的数据都是promise类型的.

初步实现

  • 先基本处理, 根据传入的参数, 遍历, 返回数据. 然后看看有什么问题
   // 这里上面promise基本实现和方法就不贴了, 可以查看系列一文章, 这里主要突出重点
   all(promises) {
       // 因为all后面也是可以进行链式调用的, 所以, 这里也需要和then一样, 返回promise的
       return new Promise((resolve, reject) => {            
           let res = []     // 用来存储处理后返回的所有结果的数组
           const length = promises.length // 传入的参数的数组长度
           promise.forEach((promise, index) =>{
           // 遍历数组中的每一个promise对象, 调用其then方法, 获取其中resolve或者reject的数据
               promise.then(result => {
               // 处理一个push一个
                   res.push(result)
               // 当res结果数组长度和传入的参数长度一致时, resolve返回一下
               if(res.length === promiseLength) resolve(res)
                   }).catch(e => reject(e))
               })
           })
       
       })
   }
  • 但是这样写, 会有一个问题, 不知道有没有被发现, 那就是, 如果传入的参数不是数组类型, 那么使用forEach会TypeError类型报错, 那么我们该怎么改正一下呢, 其实和平时我们写代码一样, 加一个类型判断就饿可以了. 那么, 下面就接上一个类型判断.
 // 这里上面promise基本实现和方法就不贴了, 可以查看系列一文章, 这里主要突出重点
 all(promises) {
     // 因为all后面也是可以进行链式调用的, 所以, 这里也需要和then一样, 返回promise的
     return new Promise((resolve, reject) => {
+           if(!Array.isArray(promises)){
+           // 坑1: 需要对传入的参数进行数据类型判断, 如果不是数组类型直接reject抛出提示信息
+               return reject('传入的参数必须得是数组格式!')
+           }
         
        let res = []     // 用来存储处理后返回的所有结果的数组
         const length = promises.length // 传入的参数的数组长度
         promise.forEach((promise, index) =>{
         // 遍历数组中的每一个promise对象, 调用其then方法, 获取其中resolve或者reject的数据
             promise.then(result => {
             // 处理一个push一个
                 res.push(result)
             // 当res结果数组长度和传入的参数长度一致时, resolve返回一下
             if(res.length === promiseLength) resolve(res)
                 }).catch(e => reject(e))
             })
         })
     })
 }
  • 到这一步, 传入的参数类型是没有问题了, 但是! 在forEach循环遍历每一个对象, 直接调用.then方法, 如果当前遍历的数据不是一个缝合规范的promise对象呢, 那这里就会进行报错了吧. 所以, 这里也需要进行数据类型的判断,如果不是promise对象, 直接返回当前遍历对象, 不进行then操作, 但是! 其实这里有一个好技巧.下面请看.
        promise.forEach((promise, index) =>{
            // 这里应该是要判断promise是否是promise格式的, 因为可能是数字等其他格式。
            // 但是这里使用了一个技巧, 利用Promise的静态方法resolve,包起来就是不管原来是不是, 反正现在是promise类型了
            let res = []
            const promiseLength= promises.length
            Promise.resolve(promise).then(result => {
                // 坑3:这里应该要有一个计数器, 因为promise.all是按照顺序返回的, 使用i就不是按照顺序了
                // 处理一个push一个
               res.push(result)
                // 当res结果数组长度和传入的参数长度一致时, resolve返回一下
                if(res.length === promiseLength) resolve(res)
              }).catch(e => reject(e))

  • 那可能有人会说了, 这样应该没啥问题了吧! 传入的参数判断类型了, 遍历的当前对象类型也处理了, 对的, 现在确实处理了传入的数据类型问题, 那么输出的注意点你注意了么? all方法返回的结果的顺序其实是和传入的参数是对应的, 不会因为其中都是异步处理导致顺序是乱的, 那么这该怎么实现呢. 下面请看:
     promise.forEach((promise, index) =>{
            // 这里应该是要判断promise是否是promise格式的, 因为可能是数字等其他格式。
            // 但是这里使用了一个技巧, 利用Promise的静态方法resolve,包起来就是不管原来是不是, 反正现在是promise类型了
            let res= []
            const promiseLength= promises.length
            Promise.resolve(promise).then(result => {
                // 坑3:这里应该要有一个计数器, 因为promise.all是按照顺序返回的, 使用i就不是按照顺序了
+                res[index] = result // 利用当前遍历的下标来存储输出结果, 保持顺序一致
                // 处理一个push一个
-               res.push(result)  // 不是直接push进去, 因为异步无法保证返回结果是按序
                // 当res结果数组长度和传入的参数长度一致时, resolve返回一下
                if(res.length === promiseLength) resolve(res)
              }).catch(e => reject(e))

  • 到了这里, 解决了输出顺序的问题, 应当没有问题了吧! 面试官就会提问:“那你有考虑过, 如果是最后一个index最先输出, 那么, 你的程序会发生啥呢?”, 这里看一下那里瞅一瞅, 心里OS:“没啥问题呀“, ”真的没问题?如果最后一个第一个输出, 那么数组长度是不是就和传入的参数长度一样了, 那是不直接就输出了“, ”对哦“, 那下面接着优化!
    promise.forEach((promise, index) =>{
            // 这里应该是要判断promise是否是promise格式的, 因为可能是数字等其他格式。
            // 但是这里使用了一个技巧, 利用Promise的静态方法resolve,包起来就是不管原来是不是, 反正现在是promise类型了
            let res= []
+           let count = 0 // 用来记录处理了多少个数据
            const promiseLength= promises.length
            Promise.resolve(promise).then(result => {
                // 坑3:这里应该要有一个计数器, 因为promise.all是按照顺序返回的, 使用i就不是按照顺序了
                res[index] = result // 利用当前遍历的下标来存储输出结果, 保持顺序一致
+               count++  // 处理一个计数器加一个
-                // 当res结果数组长度和传入的参数长度一致时, resolve返回一下
-                if(res.length === promiseLength) resolve(res)
+                if(count === promiseLength) resolve(res) // 当处理的个数和参数格式一致时,才返回
             }).catch(e => reject(e))

  • 到这里, 就将Promise.all方法全都完成了.其实, 平时使用感觉挺方便的, 但是, 也需要知道所以然, 学习学习前辈优秀的代码结晶, 哪怕对自己的有那么一点点🤏的提升也是好的.

  • 最后给上all方法的全部实现和几个注意点⚠️:

     // 这里上面promise基本实现和方法就不贴了, 可以查看系列一文章, 这里主要突出重点
     all(promises) {
         // 因为all后面也是可以进行链式调用的, 所以, 这里也需要和then一样, 返回promise的
         return new Promise((resolve, reject) => {
             if(!Array.isArray(promises)){
             // 坑1: 需要对传入的参数进行数据类型判断, 如果不是数组类型直接reject抛出提示信息
                 return reject('传入的参数必须得是数组格式!')
             }
             
             let res = []     // 用来存储处理后返回的所有结果的数组
             let count = 0    // 用来存储处理过的请求/数据的个数,用于判断是否处理完所有数据
             const length = promises.length // 传入的参数的数组长度
             promise.forEach((promise, index) =>{
             // 坑2: 这里应该是要判断promise是否是promise格式的, 因为可能是数字等其他格式。
             // 但是这里使用了一个技巧, 利用Promise的静态方法resolve,包起来就是不管原来是不是, 反正现在是promise类型了
             Promise.resolve(promise).then(result => {
                 // 坑3:这里应该要有一个计数器, 因为promise.all是按照顺序返回的, 使用i就不是按照顺序了
                 count ++
                 res[index] = result
                 // 坑4: 这里如果使用length会有错, 因为, js中数组, 如果有第7个前面没有, 长度还是7(前面是undefined)
                 if(count === promiseLength) resolve(res)
                     }).catch(e => reject(e))
                 })
             })
         
         })
     }
    

Promise的基本实现和.then方法请查看面试手写Promise系列一

前端小白,到目前, 我们已经实现了reslove, reject, then、all方法, race方法将在下篇文章输出前端小白, 有错误和不当之处, 忘各位大神指出, 如果感觉小弟码字不易, 麻烦给个👍, 谢谢!