字节飞书面试promise.all实现示例


Posted in Javascript onJune 16, 2022

前言

金三银四,身为大四即将成为毕业生的我迫不及待地将简历投进了字节的飞书部门,本想着掂量一下几斤几两,没想到这一掂就露馅了?,去大厂的梦想就这么跌倒在了Promsie.all上。但年轻人总是要有斗志的,从哪里跌到就从哪里爬起来!下面是复盘时间。

何为Promise.all?

Promise.all 是 es6 Promise 对象上的一个方法,它的功能就是将多个Promise实例包装成一个promise实例。以下是 MDN 对 Promise.all 的描述:

Promise.all() 方法接收一个 promise 的 iterable 类型(注:Array,Map,Set都属于ES6的iterable类型)的输入,并且只返回一个Promise实例, 那个输入的所有 promise 的 resolve 回调的结果是一个数组。这个Promise的 resolve 回调执行是在所有输入的 promise 的 resolve 回调都结束,或者输入的 iterable 里没有 promise 了的时候。它的 reject 回调执行是,只要任何一个输入的 promise 的 reject 回调执行或者输入不合法的 promise 就会立即抛出错误,并且reject的是第一个抛出的错误信息。

我戴上我的300度近视眼镜,仔细地提取出这段描述中的关键字

  • Promise.all 的返回值是一个新的 Promise 实例。
  • Promise.all 接受一个可遍历的数据容器,容器中每个元素都应是 Promise 实例。咱就是说,假设这个容器就是数组。
  • 数组中每个 Promise 实例都成功时(由pendding状态转化为fulfilled状态),Promise.all 才成功。这些 Promise 实例所有的 resolve 结果会按照原来的顺序集合在一个数组中作为 Promise.allresolve 的结果。
  • 数组中只要有一个 Promise 实例失败(由pendding状态转化为rejected状态),Promise.all 就失败。Promise.all.catch() 会捕获到这个 reject

原生 Promise.all 测试

咱先看看原生的Promise.all的是啥效果。

const p1 = Promise.resolve('p1')

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p2 延时一秒')
  }, 1000)
})

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p3 延时两秒')
  }, 2000)
})

const p4 = Promise.reject('p4 rejected')

const p5 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('p5 rejected 延时1.5秒')
  }, 1500)
})

// 所有Promise实例都成功
Promise.all([p1, p2, p3])
  .then(res => {
    console.log(res)
  })
  .catch(err => console.log(err)) // 2秒后打印 [ 'p1', 'p2 延时一秒', 'p3 延时两秒' ]
  
// 一个Promise实例失败
Promise.all([p1, p2, p4])
  .then(res => {
    console.log(res)
  })
  .catch(err => console.log(err)) // p4 rejected
  
// 一个延时失败的Promise
 Promise.all([p1, p2, p5])
  .then(res => {
    console.log(res)
  })
  .catch(err => console.log(err)) // 1.5秒后打印 p5 rejected
  
// 两个Promise实例失败
Promise.all([p1, p4, p5])
  .then(res => {
    console.log(res)
  })
  .catch(err => console.log(err)) // p4 rejected


复制代码

注意

上面 p4p5 在未传入 Promise.all 时需要注释掉,因为一个调用了 rejectPromise 实例如果没有使用 .catch() 方法去捕获错误会报错。但如果 Promise 实例定义了自己的 .catch,就不会触发 Promise.all.catch() 方法。

OK,理论存在,实践开始!

手动实现Promise.all

  • Promise.all 接受一个数组,返回值是一个新的 Promise 实例
Promise.MyAll = function (promises) {
  return new Promise((resolve, reject) => {

  })
}
复制代码
  • 数组中所有 Promise 实例都成功,Promise.all 才成功。不难想到,咱得需要一个数组来收集这些 Promise 实例的 resolve 结果。但有句俗话说得好:“不怕一万,就怕万一”,万一数组里面有元素不是 Promise咋办 —— 那就得用 Promise.resolve() 把它办了。这里还有一个问题,Promise 实例是不能直接调用 resolve 方法的,咱得在 .then() 中去收集结果。注意要保持结果的顺序。
Promise.MyAll = function (promises) {
  let arr = []
  return new Promise((resolve, reject) => {
    promises.forEach((item, i) => {
      Promise.resolve(item).then(res => {
        arr[i] = res
      })
    }) 
  })
}
复制代码
  • 将收集到的结果(数组arr)作为参数传给外层的 resolve 方法。这里咱们肯定是有一个判断条件的,如何判断所有 Promise 实例都成功了呢?新手容易写出这句代码(没错就是我本人了?):
if (arr.length === promises.length) resolve(arr)
复制代码

咱仔细想想 Promise 使用来干嘛的 —— 处理异步任务。对呀,异步任务很多都需要花时间呀,如果这些 Promise 中最后一个先完成呢?那 arr 数组不就只有最后一项了,前面的所有项都是 empty。所以这里咱们应该创建一个计数器,每有一个 Promise 实例成功,计数器加一:

Promise.MyAll = function (promises) {
  let arr = [],
    count = 0
  return new Promise((resolve, reject) => {
    promises.forEach((item, i) => {
      Promise.resolve(item).then(res => {
        arr[i] = res
        count += 1
        if (count === promises.length) resolve(arr)
      })
    })
  })
}
复制代码
  • 最后就是处理失败的情况了,这里有两种写法,第一种是用 .catch() 方法捕获失败:
Promise.MyAll = function (promises) {
  let arr = [],
    count = 0
  return new Promise((resolve, reject) => {
    promises.forEach((item, i) => {
      Promise.resolve(item).then(res => {
        arr[i] = res
        count += 1
        if (count === promises.length) resolve(arr)
      }).catch(reject)
    })
  })
}
复制代码

第二种写法就是给 .then() 方法传入第二个参数,这个函数是处理错误的回调函数:

Promise.MyAll = function (promises) {
  let arr = [],
    count = 0
  return new Promise((resolve, reject) => {
    promises.forEach((item, i) => {
      Promise.resolve(item).then(res => {
        arr[i] = res
        count += 1
        if (count === promises.length) resolve(arr)
      }, reject)
    })
  })
}
复制代码

测试案例

致此 Promise.all 大功告成,赶紧拿来测试一下(摩拳擦掌):

const p1 = Promise.resolve('p1')
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p2 延时一秒')
  }, 1000)
})
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p3 延时两秒')
  }, 2000)
})

const p4 = Promise.reject('p4 rejected')

const p5 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('p5 rejected 延时1.5秒')
  }, 1500)
})

// 所有 Promsie 都成功
Promise.MyAll([p1, p2, p3])
  .then(res => console.log(res))
  .catch(err => console.log(err)) // 2秒后打印 [ 'p1', 'p2 延时一秒', 'p3 延时两秒' ]
  
// 一个 Promise 失败
Promise.MyAll([p1, p2, p4])
  .then(res => console.log(res))
  .catch(err => console.log(err)) // p4 rejected
  
// 一个延时失败的 Promise
Promise.MyAll([p1, p2, p5])
  .then(res => console.log(res))
  .catch(err => console.log(err)) // 1.5秒后打印 p5 rejected 延时1.5秒
 
// 两个失败的 Promise
Promise.MyAll([p1, p4, p5])
  .then(res => console.log(res))
  .catch(err => console.log(err)) // p4 rejected
复制代码

“OhOhOhOh~~~~”,与原生的 Promise.all运行结果不能说很像,只能说一模一样。老话说的好,趁热打铁——正在火候上。我打开某个学习网站(MDN Web Docs (mozilla.org)),了解到 Promise 对象用于同时处理多个 Promise 的方法还有 Promise.racePromise.anyPromise.allSettle。从小老师就教会了咱们举一反三,仔细看了这三个方法的描述之后,我还真给反出来了?。

Promise.race

Promise.race 从字面意思理解就是赛跑,以状态变化最快的那个 Promise 实例为准,最快的 Promise 成功 Promise.race 就成功,最快的 Promise 失败 Promise.race 就失败。

咱来看看原生 Promise.race 效果

原生 Promise.race 测试

const p1 = Promise.resolve('p1')
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p2 延时一秒')
  }, 1000)
})
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p3 延时两秒')
  }, 2000)
})

const p4 = Promise.reject('p4 rejected')

const p5 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('p5 rejected 延时1秒')
  }, 1500)
})

// p1无延时,p2延时1s,p3延时2s
Promise.race([p1, p2, p3])
  .then(res => console.log(res))
  .catch(err => console.log(err)) // p1

// p4无延时reject
Promise.race([p4, p2, p3])
  .then(res => console.log(res))
  .catch(err => console.log(err)) // p4 rejected
  
// p5 延时1.5秒reject,p2延时1s
Promise.race([p5, p2, p3])
  .then(res => console.log(res))
  .catch(err => console.log(err)) // 1s后打印: p2 延时一秒
复制代码

理论存在,实践开始

手写Promise.race

整体流程与 Promise 差不多,只是对数组中的 Promise 实例处理的逻辑不一样,这里我们需要将最快改变状态的 Promise 结果作为 Promise.race 的结果,相对来说就比较简单了,代码如下:

Promise.MyRace = function (promises) {
  return new Promise((resolve, reject) => {
    // 这里不需要使用索引,只要能循环出每一项就行
    for (const item of promises) {
      Promise.resolve(item).then(resolve, reject)
    }
  })
}
复制代码

测试案例

还是刚才几个案例,咱就不重复写了?

// p1无延时,p2延时1s,p3延时2s
Promise.MyRace([p1, p2, p3])
  .then(res => console.log(res))
  .catch(err => console.log(err)) // p1

// p4无延时reject
Promise.MyRace([p4, p2, p3])
  .then(res => console.log(res))
  .catch(err => console.log(err)) // p4 rejected
  
// p5 延时1.5秒reject,p2延时1s
Promise.MyRace([p5, p2, p3])
  .then(res => console.log(res))
  .catch(err => console.log(err)) // 1s后打印: p2 延时一秒
复制代码

可以看到,结果与原生的 Promise.race 是一致的,成功!

Promise.any

Promise.anyPromise.all 可以看做是相反的。Promise.any 中只要有一个 Promise 实例成功就成功,只有当所有的 Promise 实例失败时 Promise.any 才失败,此时Promise.any 会把所有的失败/错误集合在一起,返回一个失败的 promise AggregateError类型的实例。MDN 上说这个方法还处于试验阶段,如果 node 或者浏览器版本过低可能无法使用,各位看官自行测试下。

原生 Promise.any 测试

const p1 = Promise.resolve('p1')
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p2 延时一秒')
  }, 1000)
})
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p3 延时两秒')
  }, 2000)
})

const p4 = Promise.reject('p4 rejected')

const p5 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('p5 rejected 延时1.5秒')
  }, 1500)
})

// 所有 Promise 都成功
Promise.any([p1, p2, p3])
  .then(res => console.log(res))
  .catch(err => console.log(err)) // p1
  
// 两个 Promise 成功
Promise.any([p1, p2, p4])
  .then(res => console.log(res))
  .catch(err => console.log(err)) // p1

// 只有一个延时成功的 Promise
Promise.any([p2, p4, p5])
  .then(res => console.log(res))
  .catch(err => console.log(err)) // p2 延时1秒

// 所有 Promise 都失败
Promise.any([p4, p5])
  .then(res => console.log(res))
  .catch(err => console.log(err)) // AggregateError: All promises were rejected
复制代码

可以看出,如果 Promise.any 中有多个成功的 Promise 实例,则以最快成功的那个结果作为自身 resolve 的结果。

OK,理论存在,实践开始

手写Promise.any

  • 依葫芦画瓢,咱们先写出 Promise.any 的整体结构:
Promise.MyAny = function (promises) {
  return new Promise((resolve, reject) => {
    promises.forEach((item, i) => {

    })
  })
}
复制代码
  • 这里跟Promise.all 的逻辑是反的,咱们需要收集 rejectPromise,也需要一个数组和计数器,用计数器判断是否所有的 Promise 实例都失败。另外在收集失败的 Promise 结果时咱需要打上一个失败的标记方便分析结果。
Promise.MyAny = function (promises) {
  let arr = [],
    count = 0
  return new Promise((resolve, reject) => {
    promises.forEach((item, i) => {
      Promise.resolve(item).then(resolve, err => {
        arr[i] = { status: 'rejected', val: err }
        count += 1
        if (count === promises.length) reject(new Error('没有promise成功'))
      })
    })
  })
}
复制代码

这里我没有使用 MDN 上规定的 AggregateError 实例,手写嘛,随心所欲一点,写自己看着舒服的?

测试案例

// 所有 Promise 都成功
Promise.MyAny([p1, p2, p3])
  .then(res => console.log(res))
  .catch(err => console.log(err)) // p1
  
// 两个 Promise 成功
Promise.MyAny([p1, p2, p4])
  .then(res => console.log(res))
  .catch(err => console.log(err)) // p1

// 只有一个延时成功的 Promise
Promise.MyAny([p2, p4, p5])
  .then(res => console.log(res))
  .catch(err => console.log(err)) // p2 延时1秒

// 所有 Promise 都失败
Promise.MyAny([p4, p5])
  .then(res => console.log(res))
  .catch(err => console.log(err)) // 没有promise成功
复制代码

Promise.allSettled

有时候,咱代码人总是会有点特殊的需求:如果咱希望一组 Promise 实例无论成功与否,都等它们异步操作结束了在继续执行下一步操作,这可如何是好?于是就出现了 Promise.allSettled

原生 Promise.allSettled 测试

const p1 = Promise.resolve('p1')
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p2 延时一秒')
  }, 1000)
})
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p3 延时两秒')
  }, 2000)
})

const p4 = Promise.reject('p4 rejected')

const p5 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('p5 rejected 延时1.5秒')
  }, 1500)
})

// 所有 Promise 实例都成功
Promise.allSettled([p1, p2, p3])
  .then(res => console.log(res))
  .catch(err => console.log(err)) 
// [
//   { status: 'fulfilled', value: 'p1' },
//   { status: 'fulfilled', value: 'p2 延时一秒' },
//   { status: 'fulfilled', value: 'p3 延时两秒' }
// ]

// 有一个 Promise 失败
Promise.allSettled([p1, p2, p4])
  .then(res => console.log(res))
  .catch(err => console.log(err))
// [
//   { status: 'fulfilled', value: 'p1' },
//   { status: 'fulfilled', value: 'p2 延时一秒' },
//   { status: 'rejected' , value: 'p4 rejected' }
// ]

// 所有 Promise 都失败
Promise.allSettled([p4, p5])
  .then(res => console.log(res))
  .catch(err => console.log(err))
// [
//   { status: 'rejected', reason: 'p4 rejected' },
//   { status: 'rejected', reason: 'p5 rejected 延时1.5秒' }
// ]
复制代码

可以看到,与 Promise.any 类似,Promise.allSettled 也给所有收集到的结果打上了标记。而且 Promise.allSettled 是不会变成 rejected 状态的,不管一组 Promise 实例的各自结果如何,Promise.allSettled 都会转变为 fulfilled 状态。

OK,理论存在,实践开始

手写 Promise.allSettled

咱就是说,得用个数组把所有的 Promise 实例的结果(无论成功与否)都收集起来,判断收集完了(所有 Promise 实例状态都改变了),咱就将这个收集到的结果 resolve 掉。收集成功 Promise 结果的逻辑咱们在 Promise.all 中实现过,收集失败 Promise 结果咱们在 Promise.any 中处理过。这波,这波是依葫芦画瓢——照样。

Promise.MyAllSettled = function (promises) {
  let arr = [],
    count = 0
  return new Promise((resolve, reject) => {
    promises.forEach((item, i) => {
      Promise.resolve(item).then(res => {
        arr[i] = { status: 'fulfilled', val: res }
        count += 1
        if (count === promises.length) resolve(arr)
      }, (err) => {
        arr[i] = { status: 'rejected', val: err }
        count += 1
        if (count === promises.length) resolve(arr)
      })
    })
  })
}
复制代码

这代码,逻辑上虽说没问题,但各位优秀的程序员们肯定是看不顺眼的,怎么会有两段重复的代码捏,不行,咱得封装一下。

Promise.MyAllSettled = function (promises) {
  let arr = [],
    count = 0
  return new Promise((resolve, reject) => {
    const processResult = (res, index, status) => {
      arr[index] = { status: status, val: res }
      count += 1
      if (count === promises.length) resolve(arr)
    }

    promises.forEach((item, i) => {
      Promise.resolve(item).then(res => {
        processResult(res, i, 'fulfilled')
      }, err => {
        processResult(err, i, 'rejected')
      })
    })
  })
}
复制代码

perfect,俗话说得好:没病走两步。老样子,给代码跑几个案例。

测试案例

// 所有 Promise 实例都成功
Promise.MyAllSettled([p1, p2, p3])
  .then(res => console.log(res))
  .catch(err => console.log(err)) 
// [
//   { status: 'fulfilled', value: 'p1' },
//   { status: 'fulfilled', value: 'p2 延时一秒' },
//   { status: 'fulfilled', value: 'p3 延时两秒' }
// ]

// 有一个 MyAllSettled 失败
Promise.allSettled([p1, p2, p4])
  .then(res => console.log(res))
  .catch(err => console.log(err))
// [
//   { status: 'fulfilled', value: 'p1' },
//   { status: 'fulfilled', value: 'p2 延时一秒' },
//   { status: 'rejected' , value: 'p4 rejected' }
// ]

// 所有 MyAllSettled 都失败
Promise.allSettled([p4, p5])
  .then(res => console.log(res))
  .catch(err => console.log(err))
// [
//   { status: 'rejected', reason: 'p4 rejected' },
//   { status: 'rejected', reason: 'p5 rejected 延时1.5秒' }
// ]
复制代码

致此,大功告成,我可以骄傲地对妈妈说:“妈妈,我再也不怕 Promise.all”了

结语

这次字节飞书面试对我来说是一个巨大的机遇,第一次体验面大厂的感觉,可能有暴躁老哥要说了:“字节面试题就这?你是水文章骗赞的吧”。害,没办法,主要是我太菜了,从代码不知为何物到现在前端学习者,尔来8月右一周矣,水平确实比较次,面试官比较和善,就没有为难我,问的问题都比较基础。但我仍然收获颇丰,感谢字节团队,感谢前端这个包容、进步的环境,我会好好总结这次面试,尽可能地提升自己,加油!

参考文章

因为实现不了Promise.all,一场面试凉凉了 

Promise 对象 - ECMAScript 6入门 (ruanyifeng.com)

以上就是字节飞书面试promise.all实现示例的详细内容,更多关于字节面试promise.all的资料请关注三水点靠木其它相关文章!


Tags in this post...

Javascript 相关文章推荐
jsvascript图像处理—(计算机视觉应用)图像金字塔
Jan 15 Javascript
详解JavaScript语法对{}处理的坑爹之处
Jun 05 Javascript
js实现鼠标触发图片抖动效果的方法
Feb 27 Javascript
javascript弹出带文字信息的提示框效果
Jul 19 Javascript
js对象浅拷贝和深拷贝详解
Sep 05 Javascript
Vue.js实战之利用vue-router实现跳转页面
Apr 01 Javascript
BootStrap selectpicker后台动态绑定数据的方法
Jul 28 Javascript
基于Vue实例对象的数据选项
Aug 09 Javascript
vue项目中vue-i18n和element-ui国际化开发实现过程
Apr 25 Javascript
JS实现可针对算术表达式求值的计算器功能示例
Sep 04 Javascript
JS实现换肤功能的方法实例详解
Jan 30 Javascript
关于Vue中的options选项
Mar 22 Vue.js
JS轻量级函数式编程实现XDM三
Jun 16 #Javascript
JS轻量级函数式编程实现XDM二
Jun 16 #Javascript
JS函数式编程实现XDM一
Jun 16 #Javascript
正则表达式基础与常用验证表达式
Jun 16 #Javascript
使用compose函数优化代码提高可读性及扩展性
html中两种获取标签内的值的方法
Jun 16 #jQuery
JavaScript前端面试扁平数据转tree与tree数据扁平化
Jun 14 #Javascript
You might like
PHP实现用户认证及管理完全源码
2007/03/11 PHP
详解PHP中strlen和mb_strlen函数的区别
2014/03/07 PHP
linux mint下安装phpstorm2020包括JDK部分的教程详解
2020/09/17 PHP
Firefox下提示illegal character并出现乱码的原因
2010/03/25 Javascript
JavaScript 原型链学习总结
2010/10/29 Javascript
jquery 跨域访问问题解决方法(笔记)
2011/06/08 Javascript
9行javascript代码获取QQ群成员具体实现
2013/10/16 Javascript
javascript禁制后退键(Backspace)实例代码
2013/11/15 Javascript
jquery中ajax函数执行顺序问题之如何设置同步
2014/02/28 Javascript
jQuery+slidereveal实现的面板滑动侧边展出效果
2015/03/14 Javascript
JavaScript改变CSS样式的方法汇总
2015/05/07 Javascript
jQuery平滑旋转幻灯片特效代码分享
2015/09/07 Javascript
jQuery实现内容定时切换效果完整实例
2016/04/06 Javascript
js鼠标按键事件和键盘按键事件用法实例汇总
2016/10/03 Javascript
利用jQuery.Validate异步验证用户名是否存在(推荐)
2016/12/09 Javascript
原生js轮播(仿慕课网)
2017/02/15 Javascript
AngularJs 终极购物车(实例讲解)
2017/11/08 Javascript
Node解决简单重复问题系列之Excel内容的获取
2018/01/02 Javascript
JS常见DOM节点操作示例【创建 ,插入,删除,复制,查找】
2018/05/14 Javascript
React学习之JSX与react事件实例分析
2020/01/06 Javascript
[46:47]完美世界DOTA2联赛PWL S2 FTD vs Magma 第二场 11.20
2020/11/23 DOTA
Python实现拼接多张图片的方法
2014/12/01 Python
Python学习笔记之图片人脸检测识别实例教程
2019/03/06 Python
python自动循环定时开关机(非重启)测试
2019/08/26 Python
pytorch方法测试详解——归一化(BatchNorm2d)
2020/01/15 Python
logging level级别介绍
2020/02/21 Python
很酷的HTML5电子书翻页动画特效
2016/02/25 HTML / CSS
canvas生成带二维码海报的踩坑记录
2019/09/11 HTML / CSS
英国最大的奢侈品零售网络商城:Flannels
2016/09/16 全球购物
Omio美国:全欧洲低价大巴、火车和航班搜索和比价
2017/11/08 全球购物
英国网上购买门:Direct Doors
2018/06/07 全球购物
电影T恤、80年代T恤和80年代服装:TV Store Online
2020/01/05 全球购物
c++工程师面试问题
2013/08/04 面试题
工商局所长四风自我剖析及整改措施
2014/10/26 职场文书
导师对论文的学术评语
2015/01/04 职场文书
HDFS免重启挂载新磁盘
2022/04/06 Servers