面试手写实现Promise.all

目录
  • 前言
  • 常见面试手写系列
  • Promise.resolve
    • 简要回顾
    • 源码实现
  • Promise.reject
    • 简要回顾
    • 源码实现
  • Promise.all
    • 简要回顾
    • 源码实现
  • Promise.allSettled
    • 简要回顾
    • 源码实现
  • Promise.race
    • 简单回顾
    • 源码实现
  • 结尾

前言

(?﹏?)曾经真实发生在一个朋友身上的真实事件,面试官让他手写一个Promise.all,朋友现场发挥不太好,没有写出来,事后他追问面试官给的模糊评价是基础不够扎实,原理性知识掌握较少... 当然整场面试失利,并不仅仅是这一个题目,肯定还有其他方面的原因。

但是却给我们敲响一个警钟:Promise手写实现、Promise静态方法实现早已经是面试中的高频考题,如果你对其还不甚了解,耽误你10分钟,我们一起干到他懂O(∩_∩)O

常见面试手写系列

最近很想做一件事情,希望可以将前端面试中常见的手写题写成一个系列,尝试将其中涉及到的知识和原理都讲清楚,如果你对这个系列也感兴趣,欢迎一起来学习噢,目前已有66+手写题实现啦!

1. 点击查看日拱一题源码地址(目前已有66+个手写题实现)

2.脚本之家专栏

Promise.resolve

简要回顾

  • Promise.resolve(value) 方法返回一个以给定值解析后的Promise 对象。
  • 如果这个值是一个 promise ,那么将返回这个 promise ;
  • 如果这个值是thenable(即带有"then" 方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;否则返回的promise将以此值完成。

这是MDN上的解释,我们挨个看一下

  • Promise.resolve最终结果还是一个Promise,并且与Promise.resolve(该值)传入的值息息相关
  • 传入的参数可以是一个Promise实例,那么该函数执行的结果是直接将实例返回
  • 这里最主要需要理解跟随,可以理解成Promise最终状态就是这个thenable对象输出的值

小例子

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

// 1. 非Promise对象,非thenable对象

Promise.resolve(1).then(console.log) // 1

// 2. Promise对象成功状态

const p2 = new Promise((resolve) => resolve(2))

Promise.resolve(p2).then(console.log) // 2

// 3. Promise对象失败状态

const p3 = new Promise((_, reject) => reject('err3'))

Promise.resolve(p3).catch(console.error) // err3

// 4. thenable对象

const p4 = {

  then (resolve) {

    setTimeout(() => resolve(4), 1000)

  }

}

Promise.resolve(p4).then(console.log) // 4

// 5. 啥都没传

Promise.resolve().then(console.log) // undefined

源码实现

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

Promise.myResolve = function (value) {

  // 是Promise实例,直接返回即可

  if (value && typeof value === 'object' && (value instanceof Promise)) {

    return value

  }

  // 否则其他情况一律再通过Promise包装一下

  return new Promise((resolve) => {

    resolve(value)

  })

}

// 测试一下,还是用刚才的例子

// 1. 非Promise对象,非thenable对象

Promise.myResolve(1).then(console.log) // 1

// 2. Promise对象成功状态

const p2 = new Promise((resolve) => resolve(2))

Promise.myResolve(p2).then(console.log) // 2

// 3. Promise对象失败状态

const p3 = new Promise((_, reject) => reject('err3'))

Promise.myResolve(p3).catch(console.error) // err3

// 4. thenable对象

const p4 = {

  then (resolve) {

    setTimeout(() => resolve(4), 1000)

  }

}

Promise.myResolve(p4).then(console.log) // 4

// 5. 啥都没传

Promise.myResolve().then(console.log) // undefined

疑问

从源码实现中,并没有看到对于thenable对象的特殊处理呀!其实确实也不需要在Promise.resolve中处理,真实处理的地方应该是在Promise构造函数中,如果你对这块感兴趣,马上就会写Promise的实现篇,期待你的阅读噢。

Promise.reject

简要回顾

Promise.reject() 方法返回一个带有拒绝原因的Promise对象。

?

1

2

3

4

5

6

Promise.reject(new Error('fail'))

  .then(() => console.log('Resolved'),

        (err) => console.log('Rejected', err))

// 输出以下内容       

// Rejected Error: fail

//    at <anonymous>:2:16       

源码实现

reject实现相对简单,只要返回一个新的Promise,并且将结果状态设置为拒绝就可以

?

1

2

3

4

5

6

7

8

9

10

11

Promise.myReject = function (value) {

  return new Promise((_, reject) => {

    reject(value)

  })

}

// 测试一下

Promise.myReject(new Error('fail'))

  .then(() => console.log('Resolved'),

        (err) => console.log('Rejected', err))

// Rejected Error: fail

//    at <anonymous>:9:18

Promise.all

简要回顾

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。这个静态方法应该是面试中最常见的啦

?

1

const p = Promise.all([p1, p2, p3])

最终p的状态由p1、p2、p3决定,分成两种情况。

(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

const p1 = Promise.resolve(1)

const p2 = new Promise((resolve) => {

  setTimeout(() => resolve(2), 1000)

})

const p3 = new Promise((resolve) => {

  setTimeout(() => resolve(3), 3000)

})

const p4 = Promise.reject('err4')

const p5 = Promise.reject('err5')

// 1. 所有的Promise都成功了

const p11 = Promise.all([ p1, p2, p3 ])

    .then(console.log) // [ 1, 2, 3 ]

      .catch(console.log)

// 2. 有一个Promise失败了

const p12 = Promise.all([ p1, p2, p4 ])

    .then(console.log)

      .catch(console.log) // err4

// 3. 有两个Promise失败了,可以看到最终输出的是err4,第一个失败的返回值

const p13 = Promise.all([ p1, p4, p5 ])

    .then(console.log)

      .catch(console.log) // err4

源码实现

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

Promise.myAll = (promises) => {

  return new Promise((rs, rj) => {

    // 计数器

    let count = 0

    // 存放结果

    let result = []

    const len = promises.length

    if (len === 0) {

      return rs([])

    }

    promises.forEach((p, i) => {

      // 注意有的数组项有可能不是Promise,需要手动转化一下

      Promise.resolve(p).then((res) => {

        count += 1

        // 收集每个Promise的返回值

        result[ i ] = res

        // 当所有的Promise都成功了,那么将返回的Promise结果设置为result

        if (count === len) {

          rs(result)

        }

        // 监听数组项中的Promise catch只要有一个失败,那么我们自己返回的Promise也会失败

      }).catch(rj)

    })

  })

}

// 测试一下

const p1 = Promise.resolve(1)

const p2 = new Promise((resolve) => {

  setTimeout(() => resolve(2), 1000)

})

const p3 = new Promise((resolve) => {

  setTimeout(() => resolve(3), 3000)

})

const p4 = Promise.reject('err4')

const p5 = Promise.reject('err5')

// 1. 所有的Promise都成功了

const p11 = Promise.myAll([ p1, p2, p3 ])

    .then(console.log) // [ 1, 2, 3 ]

      .catch(console.log)

// 2. 有一个Promise失败了

const p12 = Promise.myAll([ p1, p2, p4 ])

    .then(console.log)

      .catch(console.log) // err4

// 3. 有两个Promise失败了,可以看到最终输出的是err4,第一个失败的返回值

const p13 = Promise.myAll([ p1, p4, p5 ])

    .then(console.log)

      .catch(console.log) // err4

// 与原生的Promise.all返回是一致的   

Promise.allSettled

简要回顾

有时候,我们希望等到一组异步操作都结束了,不管每一个操作是成功还是失败,再进行下一步操作。显然Promise.all(其只要是一个失败了,结果即进入失败状态)不太适合,所以有了Promise.allSettled

Promise.allSettled()方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled还是rejected),返回的 Promise 对象才会发生状态变更,一旦发生状态变更,状态总是fulfilled,不会变成rejected

还是以上面的例子为例, 我们看看与Promise.all有什么不同

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

const p1 = Promise.resolve(1)

const p2 = new Promise((resolve) => {

  setTimeout(() => resolve(2), 1000)

})

const p3 = new Promise((resolve) => {

  setTimeout(() => resolve(3), 3000)

})

const p4 = Promise.reject('err4')

const p5 = Promise.reject('err5')

// 1. 所有的Promise都成功了

const p11 = Promise.allSettled([ p1, p2, p3 ])

    .then((res) => console.log(JSON.stringify(res, null,  2)))

// 输出

/*

[

  {

    "status": "fulfilled",

    "value": 1

  },

  {

    "status": "fulfilled",

    "value": 2

  },

  {

    "status": "fulfilled",

    "value": 3

  }

]

*/

// 2. 有一个Promise失败了

const p12 = Promise.allSettled([ p1, p2, p4 ])

    .then((res) => console.log(JSON.stringify(res, null,  2)))

// 输出

/*

[

  {

    "status": "fulfilled",

    "value": 1

  },

  {

    "status": "fulfilled",

    "value": 2

  },

  {

    "status": "rejected",

    "reason": "err4"

  }

]

*/

// 3. 有两个Promise失败了

const p13 = Promise.allSettled([ p1, p4, p5 ])

    .then((res) => console.log(JSON.stringify(res, null,  2)))

// 输出

/*

[

  {

    "status": "fulfilled",

    "value": 1

  },

  {

    "status": "rejected",

    "reason": "err4"

  },

  {

    "status": "rejected",

    "reason": "err5"

  }

]

*/

可以看到:

  • 不管是全部成功还是有部分失败,最终都会进入Promise.allSettled的.then回调中
  • 最后的返回值中,成功和失败的项都有status属性,成功时值是fulfilled,失败时是rejected
  • 最后的返回值中,成功含有value属性,而失败则是reason属性

源码实现

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

Promise.myAllSettled = (promises) => {

  return new Promise((rs, rj) => {

    let count = 0

    let result = []

    const len = promises.length

    // 数组是空的话,直接返回空数据

    if (len === 0) {

      return rs([])

    }

    promises.forEach((p, i) => {

      Promise.resolve(p).then((res) => {

        count += 1

        // 成功属性设置

        result[ i ] = {

          status: 'fulfilled',

          value: res

        }

        if (count === len) {

          rs(result)

        }

      }).catch((err) => {

        count += 1

        // 失败属性设置

        result[i] = {

          status: 'rejected',

          reason: err

        }

        if (count === len) {

          rs(result)

        }

      })

    })

  })

}

// 测试一下

const p1 = Promise.resolve(1)

const p2 = new Promise((resolve) => {

  setTimeout(() => resolve(2), 1000)

})

const p3 = new Promise((resolve) => {

  setTimeout(() => resolve(3), 3000)

})

const p4 = Promise.reject('err4')

const p5 = Promise.reject('err5')

// 1. 所有的Promise都成功了

const p11 = Promise.myAllSettled([ p1, p2, p3 ])

    .then((res) => console.log(JSON.stringify(res, null,  2)))

// 输出

/*

[

  {

    "status": "fulfilled",

    "value": 1

  },

  {

    "status": "fulfilled",

    "value": 2

  },

  {

    "status": "fulfilled",

    "value": 3

  }

]

*/

// 2. 有一个Promise失败了

const p12 = Promise.myAllSettled([ p1, p2, p4 ])

    .then((res) => console.log(JSON.stringify(res, null,  2)))

// 输出

/*

[

  {

    "status": "fulfilled",

    "value": 1

  },

  {

    "status": "fulfilled",

    "value": 2

  },

  {

    "status": "rejected",

    "reason": "err4"

  }

]

*/

// 3. 有两个Promise失败了

const p13 = Promise.myAllSettled([ p1, p4, p5 ])

    .then((res) => console.log(JSON.stringify(res, null,  2)))

// 输出

/*

[

  {

    "status": "fulfilled",

    "value": 1

  },

  {

    "status": "rejected",

    "reason": "err4"

  },

  {

    "status": "rejected",

    "reason": "err5"

  }

]

*/

Promise.race

简单回顾

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

?

1

const p = Promise.race([p1, p2, p3])

只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

?

1

2

3

4

5

6

7

8

9

10

11

12

const p1 = new Promise((resolve, reject) => {

  setTimeout(resolve, 500, 1)

})

const p2 = new Promise((resolve, reject) => {

  setTimeout(resolve, 100, 2)

})

Promise.race([p1, p2]).then((value) => {

  console.log(value) // 2

})

Promise.race([p1, p2, 3]).then((value) => {

  console.log(value) // 3

})

源码实现

聪明的你一定马上知道该怎么实现了,只要了解哪个实例先改变了,那么Promise.race就跟随这个结果,那么就可以写出以下代码

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

Promise.myRace = (promises) => {

  return new Promise((rs, rj) => {

    promises.forEach((p) => {

      // 对p进行一次包装,防止非Promise对象

      // 并且对齐进行监听,将我们自己返回的Promise的resolve,reject传递给p,哪个先改变状态,我们返回的Promise也将会是什么状态

      Promise.resolve(p).then(rs).catch(rj)

    })

  })

}

// 测试一下

const p1 = new Promise((resolve, reject) => {

  setTimeout(resolve, 500, 1)

})

const p2 = new Promise((resolve, reject) => {

  setTimeout(resolve, 100, 2)

})

Promise.myRace([p1, p2]).then((value) => {

  console.log(value) // 2

})

Promise.myRace([p1, p2, 3]).then((value) => {

  console.log(value) // 3

})

结尾

也许你我素未谋面,但很可能相见恨晚。希望这里能成为你的栖息之地,我愿和你一起收获喜悦,奔赴成长。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/49888.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

flink采用thrift读取tablets一个天坑

原先的配置 [INFO] StarRocksSourceBeReader [open Scan params.mem_limit 8589934592 B] [INFO] StarRocksSourceBeReader [open Scan params.query-timeout-s 600 s] [INFO] StarRocksSourceBeReader [open Scan params.keep-alive-min 100 min] [INFO] StarRocksSourceBeRea…

软件外包开发测试管理工具

测试是软件工程中非常重要的一个环节&#xff0c;在上线前必须需要经过严格的测试才能确保上线后软件系统长时间运行。有大量的软件开发和测试管理工具&#xff0c;每一个工具都有自己的特点&#xff0c;今天和大家分享一些常见的工具&#xff0c;希望对大家有所帮助。北京木奇…

MySQL中锁的简介——行级锁之 间隙锁 和 临键锁

1.间隙锁演示 2.临键锁演示 间隙锁锁住的是间隙&#xff0c;不包含对应的数据记录&#xff0c;而临键锁既会包含当前这条数据记录&#xff0c;也会锁定该数据记录之前的间隙。间隙锁的目的是防止其他事务插入间隙造成幻读现象。间隙锁是可以共存的&#xff0c;一个事务采用的间…

Redhat7/CentOS7 网络配置与管理(nmtui、nmcli、GNOME GUI、ifcfg文件、IP命令)

背景&#xff1a;作为系统管理员&#xff0c;需要经常处理主机网络问题&#xff0c;而配置与管理网络的方法和工具也有好几种&#xff0c;这里整理分享一下网络配置与管理的几种方式。 1、NetworkManager 概述 在 Red Hat Enterprise Linux 7 中&#xff0c;默认网络服务由 N…

python爬虫-加速乐cookie混淆解析实例小记

注意&#xff01;&#xff01;&#xff01;&#xff01;某XX网站逆向实例仅作为学习案例&#xff0c;禁止其他个人以及团体做谋利用途&#xff01;&#xff01;&#xff01; 第一步&#xff1a;抓包工具第一次请求页面&#xff0c;得到响应。本次我使用的fiddle进行抓包&#…

通讯录--集合动态的文件版

简易的通讯录往往需要朴实的“烹饪”就能完成一道“美味的佳肴”。 我们需要一个通讯录&#xff0c;能够存储联系人的信息&#xff0c;能够对联系人的信息进行增删查改&#xff0c;查询&#xff0c;按姓名排序。相比对之前的三子棋、扫雷&#xff0c;有了一定的了解&#xff0c…

flutter:BottomNavigationBar和TabBar

区别 BottomNavigationBarr和TabBar都是用于创建导航栏的组件&#xff0c;但它们有一些区别。 位置不同&#xff1a;BottomNavigationBar通常位于屏幕底部&#xff0c;用于主要导航&#xff1b;而TabBar通常位于屏幕顶部或底部&#xff0c;用于切换不同的视图或页面。 样式不…

[Ubuntu 22.04] containerd配置HTTP方式拉取私仓Harbor

文章目录 1. 基础环境配置2. Docker安装3. 部署Harbor&#xff0c;HTTP访问4. 部署ContainerD5. 修改docker配置文件&#xff0c;向harbor中推入镜像6. 配置containerd6.1. 拉取镜像验证6.2. 推送镜像验证 1. 基础环境配置 [Ubuntu 22.04] 安装K8S基础环境准备脚本 2. Docker安…

2023秋招面试题持续更新中。。。

目录 1.八股文渐进式MVVM三次握手&#xff0c;四次挥手viteajax组件化和模块化虚拟dom原理流程浏览器内核浏览器渲染过程回流和重绘nextTick 2.项目相关1.声明式导航和编程式导航重写push和replace方法&#xff1a;性能优化图片懒加载路由懒加载 http请求方式 1.八股文 渐进式…

shell中按照特定字符分割字符串,并且在切分后的每段内容后加上特定字符(串),然后再用特定字符拼接起来

文件中的内容&#xff0c;可以这么写&#xff1a; awk -F, -v OFS, {for(i1;i<‌NF;i){$i$i"_suffix"}}1 input.txt-F,&#xff1a;设置输入字段分隔符为逗号&#xff08;,&#xff09;&#xff0c;这将使awk按照逗号分割输入文本。-v OFS‘,’&#xff1a;设置输…

MyBatis源码分析_ResultSetHandler(7)

目录 1. 传统JDBC 2. Mybatis访问数据库 2.1 Statement访问数据库 2.2 火枪手 ResultSetHandler 出现 3. ResultSetHandler处理结果集 3.1 首先就是进入 handleResultSets 方法 3.2 handleResultSet 方法根据映射规则&#xff08;resultMap&#xff09;对结果集进行转化…

开源大模型LLaMA 2会扮演类似Android的角色么?

在AI大模型没有商业模式&#xff1f;等文章中&#xff0c;我多次表达过这样一个观点&#xff1a;不要把大模型的未来应用方式比喻成公有云&#xff0c;大模型最终会是云端操作系统的核心&#xff08;新通用计算平台&#xff09;&#xff0c;而它的落地形式会很像过去的沃森&…

linux 指令最后一期

bc ---- linux下的计算器 bc 是一个计算器 我们输入&#xff1a;quit 来退出这个计算器 我们可以这样来用&#xff1a; uname -r uname –r指令&#xff1a; 语法&#xff1a;uname [选项] 功能&#xff1a; uname用来获取电脑和操作系统的相关信息。 补充说明&#xff1a…

数据库应用:Redis安装部署

目录 一、理论 1.缓存 2.关系型数据库与非关系型数据库 3.Redis 4.Redis安装部署 5.Redis命令工具 6.Redis数据库常用命令 7.Redis多数据库操作 二、实验 1.Redis安装部署 2.Redis命令工具 3.Redis数据库命令 4.Redis多数据库操作 三、问题 1.RESP连接CentOS 7 R…

2.playbook剧本

文章目录 playbook剧本创建剧本运行剧本定义和引用变量指定远程主机sudo切换用户when条件判断剧本格式迭代with_itemswith_listwith_flattenedwith_togetherwith_cartesianwith_nested Templates模块tags模块 playbook剧本 playbooks 本身由以下各部分组成 Tasks&#xff1a;任…

操作系统启动相关概念(BIOS、MBR、GPT、BRUB)

不管是 Windows 还是 Linux 操作系统&#xff0c;底层设备一般均为物理硬件&#xff0c;操作系统启动之前会对硬件进行检测&#xff0c;然后硬盘引导启动操作系统&#xff0c;如下为操作系统启动相关的各个概念。 一、BIOS 基本输入输出系统&#xff08;Basic Input Output Sy…

【Java】Spring——创建Spring + 对Spring的存储 /读取对象操作

文章目录 前言一、创建Spring项目二、向Spring容器中存储 Bean 对象三、从Spring容器中读取 Bean 对象得到Spring上下文对象得到 Bean 对象 总结 前言 本人是一个普通程序猿!分享一点自己的见解,如果有错误的地方欢迎各位大佬莅临指导,如果你也对编程感兴趣的话&#xff0c;互…

决策树学习

决策树学习 决策树决策树基础适用决策树学习的经典目标问题样本的表示训练样本决策树的概念发展历史 经典决策树算法ID3算法属性选择和节点混杂度&#xff08;Impurity&#xff09;ID3 Q1: 哪个属性是最佳属性&#xff1f;当前最佳属性节点选择熵&#xff08;Entropy&#xff0…

JSP 中的隐式对象预定义变量详解

JSP隐式对象是JSP容器为每个页面提供的Java对象&#xff0c;开发者可以直接使用它们而不用显式声明。JSP隐式对象也被称为预定义变量。 JSP所支持的九大隐式对象&#xff1a; request对象 request对象是javax.servlet.http.HttpServletRequest 类的示例。每当客户端请求一个J…

揭秘UniApp跨平台魔力:6道面试题带你探索!

文章目录 1. UniApp是什么&#xff1f;它有什么特点和优势&#xff1f;2. 介绍一下UniApp的跨平台原理。1. 基于WebView的渲染2. 统一封装API3. 平台特性适配4. 虚拟DOM Diff算法 3. 如何在UniApp中实现页面路由跳转&#xff1f;4. 如何在UniApp中发送网络请求&#xff1f;5. 详…