365出款成功未到

保姆级手写promise以及promise常用得方法

保姆级手写promise以及promise常用得方法

最近迷上了promise,觉得还挺有意思,看了大佬的一篇知乎文章 面试官:“你能手写一个 Promise 吗” ,写的很详细,超级赞,看完了来着做个笔记,牢固一下理解和印象。

1. 常见 Promise 面试题

这个模块是搬运 面试官:“你能手写一个 Promise 吗” 文章,不想到时候看还得2个文章切换,所以就把这块搬运过来。

Promise 解决了什么问题?

Promise 常用的 API 有哪些?

能不能手写一个符合 Promise/A+ 规范的 Promise?

Promise 有什么缺陷,可以如何解决?

1.1 Promise 出现的原因 & 业界实现

在 Promise 出现以前,在我们处理多个异步请求嵌套时,代码往往是这样的。。。

1 let fs = require('fs')

2

3 fs.readFile('./name.txt','utf8',function(err,data){

4 fs.readFile(data, 'utf8',function(err,data){

5 fs.readFile(data,'utf8',function(err,data){

6 console.log(data);

7 })

8 })

9 })

为了获取回调得结果,就必须得这样一层嵌套一层,使得代码变的更加难以阅读和难以维护,这就是传说中臭名昭著的回调地狱,产生回调地狱的原因:

嵌套调用,第一个函数的输出往往是第二个函数的输入;

处理多个异步请求并发,开发时往往需要同步请求最终的结果。

Promise用比较友好的方式解决以上2个问题,用then链式解决嵌套问题,用Promise.all解决异步请求并发的问题。

2. 开始手搓Promise啦

手搓Promise,不是一蹴而就的,慢慢来,先搞简单的版本1,然后再慢慢升级,向Promise靠拢。

Promise核心列一下,不然要实现什么我们都不知道。

它有3个状态,pending、fulfilled、rejected,一经改变不可逆转

new Promise接受一个executor()执行器,并立即执行

executor接受resolve和reject

then方法,接受2个参数,成功执行参数1,onFulfilled,参数是成功的值value;失败执行参数2, onRejected,参数是失败的值reason;

then链式调用,值的穿透

catch、finally

all,race,any,allsettled常用方法

2.1 手搓PromiseV1.0.0

第一个版本,我们就希望能让以上示例跑起来,打开对应的注释,就会打印对应的内容。开敲!

我们首先根据上面分析的promise核心和我们想要实现的版本1示例,把大概的架子搭出来了。

然后我们继续往下走,resolve就是成功的,reject就是失败的,所以resolve和reject这2个方法,要做的事情就是改变promise的状态,并把对应状态的值给赋上去。

好的,resolve和reject该做的事情也都做了,接下来是then,then接受2个方法参数,成功的就执行方法1,并把成功的值value作为参数;失败的就执行方法2,并把失败的值reason作为参数。

接下来我们执行一下,看看版本1的示例能不能跑起来。

恭喜恭喜!喜提V1.0.0的手搓promise,代码如下:

1 /* V1.0.0 promise*/

2 /* promise的3个状态 */

3 const PENDING = 'PENDING';

4 const FULFILLED = 'FULFILLED';

5 const REJECTED = 'REJECTED';

6

7 class MyPromise {

8 constructor(executor) {

9 this.status = PENDING; // 初始的状态

10 this.value = undefined; // 成功的值

11 this.reason = undefined; // 失败的值

12 let resolve = value => {

13 /* 防止重复执行,表示状态不可逆 */

14 if (this.status === PENDING) {

15 this.value = value;

16 this.status = FULFILLED;

17 }

18 };

19 let reject = reason => {

20 /* 防止重复执行,表示状态不可逆 */

21 if (this.status === PENDING) {

22 this.reason = reason;

23 this.status = REJECTED;

24 }

25 };

26 /* 立即执行 executor*/

27 try {

28 executor(resolve, reject);

29 } catch (e) {

30 reject(e);

31 }

32 }

33 then(onFulfilled, onRejected) {

34 if (this.status === FULFILLED) {

35 onFulfilled(this.value);

36 }

37 if (this.status === REJECTED) {

38 onRejected(this.reason);

39 }

40 }

41 }

42

43 new MyPromise((resolve, reject) => {

44 // resolve('成功啦!');

45 reject('失败啦');

46 }).then(

47 res => {

48 console.log('成功:', res);

49 },

50 err => {

51 console.log('失败:', err);

52 }

53 );

2.2 手搓PromiseV1.1.0

我们已经实现了一个基础版的 Promise,但是还不要高兴的太早噢,这里我们只处理了同步操作的 promise。如果在 executor()中传入一个异步操作的话呢,我们试一下:

我们发现,传一个异步操作时候,没任何打印,为什么?因为我们在执行then的时候,这个promise的状态还是pending,pending时候不会执行then的2个方法参数的任何一个,所以就没得反应。换句话说,promise只有执行了resolve或reject,状态改变了,才会执行then。所以如果当调用 then 方法时,当前状态是 pending,我们需要先将成功和失败的回调分别存放起来,在executor()的异步任务被执行时,触发 resolve 或 reject,依次调用成功或失败的回调。升级了朋友们!

恭喜恭喜!解决了传入异步的问题,喜提V1.1.0的手搓promise,代码如下:

1 /* promise的3个状态 */

2 const PENDING = 'PENDING';

3 const FULFILLED = 'FULFILLED';

4 const REJECTED = 'REJECTED';

5

6 class MyPromise {

7 constructor(executor) {

8 this.status = PENDING; // 初始的状态

9 this.value = undefined; // 成功的值

10 this.reason = undefined; // 失败的值

11 this.onFulfilledCallbacks = []; // 存放成功的回调

12 this.onRejectedCallbacks = []; // 存放失败的回调

13 let resolve = value => {

14 /* 防止重复执行,表示状态不可逆 */

15 if (this.status === PENDING) {

16 this.value = value;

17 this.status = FULFILLED;

18 this.onFulfilledCallbacks.forEach(fn => fn());

19 }

20 };

21 let reject = reason => {

22 /* 防止重复执行,表示状态不可逆 */

23 if (this.status === PENDING) {

24 this.reason = reason;

25 this.status = REJECTED;

26 this.onRejectedCallbacks.forEach(fn => fn());

27 }

28 };

29 /* 立即执行 executor*/

30 try {

31 executor(resolve, reject);

32 } catch (e) {

33 reject(e);

34 }

35 }

36 then(onFulfilled, onRejected) {

37 if (this.status === FULFILLED) {

38 onFulfilled(this.value);

39 }

40 if (this.status === REJECTED) {

41 onRejected(this.reason);

42 }

43 if (this.status === PENDING) {

44 this.onFulfilledCallbacks.push(() => onFulfilled(this.value));

45 this.onRejectedCallbacks.push(() => onRejected(this.reason));

46 }

47 }

48 }

49

50 new MyPromise((resolve, reject) => {

51 setTimeout(() => {

52 resolve('异步成功啦!');

53 // reject('失败啦');

54 }, 1000);

55 }).then(

56 res => {

57 console.log('成功:', res);

58 },

59 err => {

60 console.log('失败:', err);

61 }

62 );

2.3 手搓PromiseV2.0.0

我们开始要接触核心区了朋友们!promise最关键的还是它的then链式调用和值的穿透。

我们先来看看then链式调用,它可以一直.then,为什么?因为.then返回的也是一个promise呀,promise里面有then方法,当然可以继续往下点咯。所以

关键点1:then返回的是一个promise,

关键点2:上一次返回的值可以作为下一个then方法执行的参数

【promise2】里面该写啥呢?首先,上面版本做的事情,肯定要做进去的:执行【onFulfilled】或者【onRejected】,pending时候要存一下回调方法,其次,我们上面有说过,想要执行then,一定得执行resolve或reject,把状态改变了,才能执行then,所以我们手搓的then方法中,promise2一定要执行resolve或者reject才行,而且resolve或reject的参数,是下一次then【onFulfilled】或者【onRejected】返回的值。

then链式回调算是做了一个粗糙的版本了,但是那个resolvePromise方法里面的【resolve(x)】太粗糙啦,如果x里面有then,我们是要继续执行的,then里面返回的值才是下个then的方法参数,我们看看MDN里面的promise。

再看看把这个示例,用我们手写的promise打印出来的值

所以我们的resolvePromise需要更加精细一点,再打磨打磨一下。

then链式了,还有值穿透哦,就是then没做任何处理的时候,值会往下传。

以上,手写promise算是完成,下面就是扩展一下,在原型上增加catch、finally、all、race等等。

1 /* promise的3个状态 */

2 const PENDING = 'PENDING';

3 const FULFILLED = 'FULFILLED';

4 const REJECTED = 'REJECTED';

5 const resolvePromise = (promise2, x, resolve, reject) => {

6 // 自己等待自己完成是错误的实现,用一个类型错误,结束掉 promise Promise/A+ 2.3.1

7 if (promise2 === x) {

8 return reject(new TypeError('Chaining cycle detected for promise #'));

9 }

10 // Promise/A+ 2.3.3.3.3 只能调用一次

11 let called;

12 if ((typeof x === 'object' && x !== null) || typeof x === 'function') {

13 try {

14 let then = x.then;

15 if (typeof then === 'function') {

16 then.call(

17 x,

18 y => {

19 if (called) return;

20 called = true;

21 resolvePromise(promise2, y, resolve, reject);

22 },

23 r => {

24 if (called) return;

25 called = true;

26 reject(r);

27 }

28 );

29 } else {

30 resolve(x);

31 }

32 } catch (e) {

33 if (called) return;

34 called = true;

35 reject(e);

36 }

37 } else {

38 resolve(x);

39 }

40 };

41 class MyPromise {

42 constructor(executor) {

43 this.status = PENDING; // 初始的状态

44 this.value = undefined; // 成功的值

45 this.reason = undefined; // 失败的值

46 this.onFulfilledCallbacks = []; // 存放成功的回调

47 this.onRejectedCallbacks = []; // 存放失败的回调

48 let resolve = value => {

49 /* 防止重复执行,表示状态不可逆 */

50 if (this.status === PENDING) {

51 this.value = value;

52 this.status = FULFILLED;

53 this.onFulfilledCallbacks.forEach(fn => fn());

54 }

55 };

56 let reject = reason => {

57 /* 防止重复执行,表示状态不可逆 */

58 if (this.status === PENDING) {

59 this.reason = reason;

60 this.status = REJECTED;

61 this.onRejectedCallbacks.forEach(fn => fn());

62 }

63 };

64 /* 立即执行 executor*/

65 try {

66 executor(resolve, reject);

67 } catch (e) {

68 reject(e);

69 }

70 }

71 then(onFulfilled, onRejected) {

72 /* 穿透就是没有方法,则给一个默认的方法,将值传下去 */

73 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;

74 /* !!!注意啊,reject的方法空时不可以写 v=>v,不然永远都不会走catch,应该v=>{throw v}*/

75 onRejected =

76 typeof onRejected === 'function'

77 ? onRejected

78 : err => {

79 throw err;

80 };

81 let promise2 = new MyPromise((resolve, reject) => {

82 let commonDo = status => {

83 let x;

84 setTimeout(() => {

85 try {

86 if (status === FULFILLED) {

87 x = onFulfilled(this.value);

88 }

89 if (status === REJECTED) {

90 x = onRejected(this.reason);

91 }

92 resolvePromise(promise2, x, resolve, reject);

93 } catch (e) {

94 reject(e);

95 }

96 }, 0);

97 };

98 if (this.status === FULFILLED) {

99 commonDo(this.status);

100 }

101 if (this.status === REJECTED) {

102 commonDo(this.status);

103 }

104 if (this.status === PENDING) {

105 this.onFulfilledCallbacks.push(() => commonDo(FULFILLED));

106 this.onRejectedCallbacks.push(() => commonDo(REJECTED));

107 }

108 });

109 return promise2;

110 }

111 }

112

113 new MyPromise(resolve => {

114 resolve(1);

115 })

116 .then(res => {

117 console.log(res);

118 return {

119 then: resolve => {

120 return resolve(2);

121 },

122 };

123 })

124 .then(res => {

125 console.log(res);

126 return new Promise((resolve, reject) => {

127 reject('3错误');

128 });

129 })

130 .then(

131 res => {

132 console.log(res);

133 },

134 err => {

135 console.log('错误:', err);

136 }

137 );

3. 扩展Promise的api

3.1 Promise.prototype.resolve

1 static resolve(value) {

2 return new MyPromise((resolve, reject) => {

3 resolve(value);

4 });

5 }

resolve 要稍微调整一下,因为promise.resolve 是具备等待功能的。

1 let resolve = value => {

2 if (value instanceof MyPromise) {

3 return value.then(resolve, reject);

4 }

5 /* 防止重复执行,表示状态不可逆 */

6 if (this.status === PENDING) {

7 this.value = value;

8 this.status = FULFILLED;

9 this.onFulfilledCallbacks.forEach(fn => fn());

10 }

11 };

3.2 Promise.prototype.reject

1 static reject(reason) {

2 return new MyPromise((resolve, reject) => {

3 reject(reason);

4 });

5 }

3.3 Promise.prototype.catch

catch就是特殊的then,第一个参数传空就行。

1 catch(onRejected) {

2 this.then(null, onRejected);

3 }

3.4 Promise.prototype.finally

finally,不论promise的状态是fulfilled还是rejected,都会执行finally,而且finally上面返回的结果,会是下面执行的参数。

1 finally(callback) {

2 return this.then(

3 value => {

4 return MyPromise.resolve(callback()).then(() => value);

5 },

6 reason => {

7 return MyPromise.resolve(callback()).then(() => {

8 throw reason;

9 });

10 }

11 );

12 }

之前有写过一篇 Promise常用的方法,对以下常用方法给出了一些说明。

3.5 Promise.prototype.all

接受promise数组,都成功时,按照顺序返回,有一个失败就算失败。

1 MyPromise.all = function (promises) {

2 return new MyPromise((resolve, reject) => {

3 let arr = [];

4 promises.forEach((promise, index) => {

5 MyPromise.resolve(promise)

6 .then(res => {

7 arr[index] = res;

8 // 只有都请求成功了,才算成功

9 if (Object.keys(arr).length === promises.length) {

10 resolve(arr);

11 }

12 })

13 .catch(err => {

14 // 一个失败就算失败

15 reject(err);

16 });

17 });

18 });

19 };

3.6 Promise.prototype.any

接受一个promise数组,有一个成功,则立马resolve执行then,只有全部失败才执行catch,并返回一个返回一个失败的 promise 和AggregateError类型的实例。

3.7 Promise.prototype.race

接受一个数组,返回给出回应最快的那个,不论这个回应是resolve还是reject。

1 MyPromise.race = function (promises) {

2 return new MyPromise((resolve, reject) => {

3 promises.forEach(promise => {

4 MyPromise.resolve(promise)

5 .then(res => {

6 // 成功的立马返回

7 resolve(res);

8 })

9 .catch(err => {

10 // 失败的立马返回

11 reject(err);

12 });

13 });

14 });

15 };

3.8 Promise.prototype.allSettled

接受一个数组,但它不会执行.catch,只会执行.then,也是等所有请求结束后,返回一个数组,数组里的每项与参数的数组每项一一对应,返回的每项包含字段:

status:状态(rejected/fulfilled),value:成功返回的值,reason:失败返回的值。

我一般用它的场景是页面中有多个表单需要校验,这样多个表单的validate方法就是一个数组,allSettled方法的参数,然后我可以找到第几个表单校验失败。

1 MyPromise.allSettled = function (promises) {

2 return new MyPromise((resolve, reject) => {

3 let arr = [];

4 promises.forEach((promise, index) => {

5 MyPromise.resolve(promise)

6 .then(res => {

7 // 成功的

8 arr[index] = {

9 status: 'fulfilled',

10 value: res,

11 };

12 })

13 .catch(err => {

14 // 失败的

15 arr[index] = {

16 status: 'rejected',

17 reason: err,

18 };

19 })

20 .finally(() => {

21 // 所有请求都完成,那么resolve

22 if (Object.keys(arr).length === promises.length) {

23 resolve(arr);

24 }

25 });

26 });

27 });

28 };

4. Promise 有什么缺陷,可以如何解决?

Promise 是没有中断方法的,xhr.abort()、ajax 有自己的中断方法,一种是设置超时时间让ajax自动断开,一种是手动去停止ajax请求,其核心是调用XMLHttpRequest对象上的abort方法,axios 是基于 ajax 实现的;fetch 基于 promise,所以他的请求是无法中断的。

我们可以利用promise的race,来自己封装一个中断的方法:

1 function wrap(promise) {

2 let abort;

3 let newPromise = new MyPromise((resolve, reject) => {

4 abort = reject;

5 // 假设我们设置的超时时间是2000ms,超过就超时失败

6 setTimeout(() => {

7 reject('超时了');

8 }, 2000);

9 });

10 let p = MyPromise.race([promise, newPromise]);

11 p.abort = abort;

12 return p;

13 }

好啦,promise的分享就到这里啦,有新的见解可以一起讨论呀,互相学习。

参考

面试官:“你能手写一个 Promise 吗”

相关推荐

牧童原文、翻译及赏析、拼音版及朗读
365网站是正规平台吗

牧童原文、翻译及赏析、拼音版及朗读

📅 2025-10-15 👁️ 1558
探索VR传奇世界:沉浸式传奇体验的未来
365网站是正规平台吗

探索VR传奇世界:沉浸式传奇体验的未来

📅 2025-09-05 👁️ 7980
咸阳市武功县武功客运站足疗按摩商家热度关注排名前十榜单,足疗按摩声誉很好的商家!
明孝陵的“明”字,为何被写成了“眀”,是暗讽明朝有月无日吗?