Skip to content
目录

Promise类

完整代码

ts
enum Status {
  PENDING = "pending",
  REJECTED = "rejected",
  RESOLVED = "resolved",
}

class MyPromise {
  protected promiseResult: any = null;
  protected promiseStatus: Status = Status.PENDING;
  protected onFulFilledCallback: Array<any> = [];
  protected onRejectedCallback: Array<any> = [];

  static all(promises: Array<any>) {
    const result: any[] = [];
    if(promises.length === 0) {
        return new MyPromise(resolve => resolve(result));
    }
    let count = 0;
    return new MyPromise((resolve, reject) => {
      const addData = (value: any, index: number) => {
        result[index] = value;
        count++;
        if (count === promises.length) {
          resolve(result);
        }
      };
      promises.forEach((promise, index) => {
        if (promise instanceof MyPromise) {
          promise.then(
            (value) => {
              addData(value, index);
            },
            (reason) => {
              reject(reason);
            }
          );
        } else {
          addData(promise, index);
        }
      });
    });
  }

  static race(promises: Array<any>) {
    return new MyPromise((resolve, reject) => {
      promises.forEach((promise) => {
        if (promise instanceof MyPromise) {
          promise.then(
            (value) => {
              resolve(value);
            },
            (reason) => {
              reject(reason);
            }
          );
        } else {
          resolve(promise);
        }
      });
    });
  }

  static any(promises: Array<any>) {
    let count = 0;
    return new MyPromise((resolve, reject) => {
      promises.forEach((promise) => {
        if (promise instanceof MyPromise) {
          promise.then(
            (value) => {
              resolve(value);
            },
            () => {
              count++;
              if (count === promises.length) {
                reject(new Error("All promises were rejected"));
              }
            }
          );
        } else {
          resolve(promise);
        }
      });
    });
  }

  constructor(
    executor: (
      resolve: (value: any) => void,
      reject: (reason: any) => void
    ) => void
  ) {
    try {
      this.resolve = this.resolve.bind(this);
      this.reject = this.reject.bind(this);
      executor(this.resolve, this.reject);
    } catch (e) {
      this.reject(e);
    }
  }

  resolve(value: any) {
    if (this.promiseStatus !== Status.PENDING) {
      return;
    }
    this.promiseResult = value;
    this.promiseStatus = Status.RESOLVED;
    while (this.onFulFilledCallback.length) {
      this.onFulFilledCallback.shift()(this.promiseResult);
    }
  }

  reject(reason: any) {
    if (this.promiseStatus !== Status.PENDING) {
      return;
    }
    this.promiseResult = reason;
    this.promiseStatus = Status.REJECTED;
    while (this.onFulFilledCallback.length) {
      this.onRejectedCallback.shift()(this.promiseResult);
    }
  }

  then(onFulFilled?: (value: any) => any, onRejected?: (reason: any) => any) {
    onFulFilled =
      typeof onFulFilled === "function" ? onFulFilled : (value) => value;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (reason) => {
            throw new Error(`${reason}`);
          };
    const thenPromise = new MyPromise((resolve, reject) => {
      const resolvePromise = (callback: any) => {
        queueMicrotask(() => {
          try {
            const x = callback(this.promiseResult);
            if (x === thenPromise && x != undefined) {
              throw new Error(`不能返回自身`);
            } else if (x instanceof MyPromise) {
              x.then(resolve, reject);
            } else {
              resolve(x);
            }
          } catch (e) {
            reject(e);
            throw new Error(`${e}`);
          }
        });
      };
      if (this.promiseStatus === Status.RESOLVED) {
        resolvePromise(onFulFilled);
      } else if (this.promiseStatus === Status.REJECTED) {
        resolvePromise(onRejected);
      } else if (this.promiseStatus === Status.PENDING) {
        this.onFulFilledCallback.push(resolvePromise.bind(this, onFulFilled));
        this.onRejectedCallback.push(resolvePromise.bind(this, onRejected));
      }
    });
    return thenPromise;
  }
}
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161

建议先阅读《Promise A+》文档,本文中也会列出部分在文档中的出处

Callback数组的作用

为什么会存在成功和失败的两个回调数组?

直接在.then方法中判断状态返回this.promiseResult好像也是可行的?

ts
class MyPromise {
    protected onFulFilledCallback: Array<any> = [];
	protected onRejectedCallback: Array<any> = [];
}
1
2
3
4

我们先思考一下,promise状态改变和.then方法执行的先后关系,有以下两种:

  • 先改变状态,再执行.then方法
  • 先执行.then方法,再改变状态

第一种是最普遍的,也许你对第二种场景比较陌生,这里我们构造一个调用场景

ts
const p = new MyPromise((resolve, reject) => {
	setTimeout(resolve, 1000, 1);
});
p.then(console.log, console.error);
1
2
3
4

根据Event loop和浏览器处理任务的顺序

setTimeout(宏任务)会在下一个Tick被执行

也就是说这个promise先执行了.then方法,再执行状态改变函数resolve(1)

当你执行.then方法时,promise状态还未被改变,此时的状态为PENDING

所以我们可以得出一个结论:

.then方法的回调函数应该在promise状态被改变时调用也就是在resolve/reject函数中。

这就是我们设计Callback数组的作用,将.then方法中传入的onFulfilledCallbackonRejectedCallback保存起来,在状态改变时依次执行对应状态的回调数组

至于为什么是数组?

ts
const p = new Promise(resolve=>{
	setTimeout(resolve, 1000, 1);
})
p.then(console.log);
p.then(console.error);
1
2
3
4
5

promise本来就可以多次执行.then方法

其实这一点在Promise A+规范中早就给出了

《Promise A+规范》

2.2.2. 如果onFulfilled是一个函数

  • 2.2.2.1. 它必须在promise解决后调用,promise的值作为它的第一个参数。
  • 2.2.2.2. 它一定不能在promise解决前调用。
  • 2.2.2.3. 它一定不能被调用多次。

2.2.3. 如果onRejected是一个函数

  • 2.2.3.1. 它必须在promise拒绝之后调用,用promise的原因作为它的第一个参数。
  • 2.2.3.2. 它一定不能在promise拒绝之前调用。
  • 2.2.3.3. 它一定不能被调用多次。

2.2.6. 同一个promise上的then可能被调用多次

  • 2.2.6.1. 如果promise解决,所有相应的onFulfilled回调必须按照他们原始调用then的顺序执行
  • 2.2.6.2. 如果promise拒绝,所有相应的onRejected回调必须按照他们原始调用then的顺序执行

.then方法的链式调用

在整个Promise类中最为核心的莫过于.then方法了,.then方法会返回一个promise

promiseThen

《Promise A+规范》

2.2.7. then必须返回一个promise [3.3]

js
promise2 = promise1.then(onFulfilled,onRejected)
1
  • 2.2.7.1. 如果onFulfilledonRjected返回一个值x,运行promise解决程序[[Resolve]](promise2,x)
  • 2.2.7.2. 如果onFulfilledonRejected抛出一个异常epromise2必须用e作为原因被拒绝
  • 2.2.7.3. 如果onFulfilled不是一个函数并且promise1解决promise2必须用与promise1相同的值被解决
  • 2.2.7.4. 如果onRejected不是一个函数并且promise1拒绝promise2必须用与promise1相同的原因被拒绝

第一步直接自信返回一个promise

这里我们约定初始的promise为originPromise,then返回的promise为thenPromise

ts
then(onFulFilled?: (value: any) => any, onRejected?: (reason: any) => any) {
    const thenPromise = new MyPromise((resolve, reject) => {
        ……
        if (this.promiseStatus === Status.RESOLVED) {
            // 状态为resolved逻辑
        } else if (this.promiseStatus === Status.REJECTED) {
            // 状态为rejected逻辑
        } else if (this.promiseStatus === Status.PENDING) {
            // 状态为pending逻辑
        }
        ……
    });
    return thenPromise;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

对于三种情况的逻辑如下:

  • 如果此时状态已经改变(成功或失败),直接执行对应的回调函数
  • 如果状态为PENDING,将onFulFilledonRejected放入对应回调数组

状态改变时对应的callback的执行是必然的,我们需要着重考虑链式调用时.then方法返回一个新的promise,这个promise将决定thenPromise的状态

《Promise A+规范》

2.3. Promise解决程序

promise解决程序是一个抽象操作,它以一个promise和一个值作为输入,我们将其表示为[[Resolve]](promise, x)。如果x是一个thenable,它尝试让promise采用x的状态,并假设x的行为至少在某种程度上类似于promise。否则,它将会用值x解决 promise

这种thenable的特性使得Promise的实现更具有通用性:只要其暴露一个遵循Promise/A+协议的then方法即可。这同时也使遵循Promise/A+规范的实现可以与那些不太规范但可用的实现能良好共存。

要运行[[Resolve]](promise, x),需要执行如下步骤:

  • 2.3.1. 如果promisex引用同一个对象,用一个TypeError作为原因来拒绝promise

  • 2.3.2. 如果x是一个promise,采用它的状态:

    • 2.3.2.1. 如果x等待态,promise必须保持等待状态,直到x解决拒绝
    • 2.3.2.2. 如果x解决态,用相同的值解决promise
    • 2.3.2.3. 如果x拒绝态,用相同的原因拒绝promise
  • 2.3.3. 否则,如果x是一个对象或函数

    • 2.3.3.1. 让then成为x.then
    • 2.3.3.2. 如果检索属性x.then导致抛出了一个异常e,用e作为原因拒绝promise
  • 2.3.4. 如果x不是一个对象或函数,用x解决promise

如果promise用一个循环的thenable解决,由于[[Resolve]](promise, thenalbe)的递归特性,最终将导致[[Resolve]](promise, thenable)被再次调用,遵循上面的算法将会导致无限递归。规范中并没有强制要求处理这种情况,但也鼓励实现者检测这样的递归是否存在,并且用一个信息丰富的TypeError作为原因拒绝promise

ts
// 变量名与上面Promise A+规范对应(不是我不想写的语义化)
try {
    const x = callback(this.promiseResult);
    if (x === thenPromise && x != undefined) {
        throw new Error(`不能返回自身`);
    } else if (x instanceof MyPromise) {
        /*
        这里对于刚接触前端的同学来讲可能不够清晰,展开如下。它表明thenPromise的状态由x决定
        x.then(value=>{
        	resolve(value)
        }, reason=>{
        	reject(reason)
        });
        */
        x.then(resolve, reject);
    } else {
        resolve(x);
    }
} catch (e) {
    // 这里既reject(e)又throw Error的原因先按下不表,在后面我们会做出解释
    reject(e);
    throw new Error(`${e}`);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

这里讲一个插曲,有一个实验室学弟理解then方法中是一个递归调用,我也纳闷哪来的递归呢?后来仔细想的话还真有递归。

总所周知递归需要有一个结束条件,否则就是无线递归。这里如果返回的promise是自身的话,就会是无限递归

简单把这段代码写成一个函数

ts
const resolvePromise = (callback: any) => {
    // 微任务队列
    queueMicrotask(() => {
        try {
            const x = callback(this.promiseResult);
            if (x === thenPromise && x != undefined) {
                throw new Error(`不能返回自身`);
            } else if (x instanceof MyPromise) {
                x.then(resolve, reject);
            } else {
                resolve(x);
            }
        } catch (error) {
            reject(error);
            throw new Error(`${error}`);
        }
    });
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

在上述.then方法的状态判断逻辑中,就应该是

ts
if (this.promiseStatus === Status.RESOLVED) {
    resolvePromise(onFulFilled);
} else if (this.promiseStatus === Status.REJECTED) {
    resolvePromise(onRejected);
} else if (this.promiseStatus === Status.PENDING) {
    this.onFulFilledCallback.push(resolvePromise.bind(this, onFulFilled));
    this.onRejectedCallback.push(resolvePromise.bind(this, onRejected));
}
1
2
3
4
5
6
7
8

这样一个简单的promise链式调用就完成了

错误穿透/异常穿透

接下来我们来看看一些与链式调用无关的代码

ts
then(onFulFilled?: (value: any) => any, onRejected?: (reason: any) => any) {
    onFulFilled =
      typeof onFulFilled === "function" ? onFulFilled : (value) => value;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (reason) => {
            throw new Error(`${reason}`);
          };
    ……
  }
1
2
3
4
5
6
7
8
9
10
11

这一段代码有什么作用?没有他们又会怎样?

《Promise A+规范》

2.2.1 onFulfilledonRejected都是可选的参数

  • 2.2.1.1. 如果onFulfilled不是一个函数,它必须被忽略
  • 2.2.1.2. 如果onRejected不是一个函数,它必须被忽略

但传入的非函数时,会将其赋值为一个默认的函数。

onFulfilled较好理解,promise如果没有抛错或者返回一个被拒绝的promise时会返回一个成功的promise,并且promiseResult值为value

onRejected为什么要抛错呢?

我们仔细观察一下这个onRejected的抛错会在哪里被捕获

ts
constructor(
    executor: (
    resolve: (value: any) => void,
    reject: (reason: any) => void
    ) => void
    ) {
        try {
            this.resolve = this.resolve.bind(this);
            this.reject = this.reject.bind(this);
            executor(this.resolve, this.reject);
        } catch (e) {
            this.reject(e);
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
ts
reject(reason: any) {
    if (this.promiseStatus !== Status.PENDING) {
        return;
    }
    this.promiseResult = reason;
    this.promiseStatus = Status.REJECTED;
    while (this.onFulFilledCallback.length) {
        this.onRejectedCallback.shift()(this.promiseResult);
    }
}
1
2
3
4
5
6
7
8
9
10
ts
const resolvePromise = (callback: any) => {
    // 微任务队列
    queueMicrotask(() => {
        try {
            const x = callback(this.promiseResult);
            if (x === thenPromise && x != undefined) {
                throw new Error(`不能返回自身`);
            } else if (x instanceof MyPromise) {
                x.then(resolve, reject);
            } else {
                resolve(x);
            }
        } catch (e) {
            reject(e);
            throw new Error(`${e}`);
        }
    });
};
if (this.promiseStatus === Status.RESOLVED) {
    resolvePromise(onFulFilled);
} else if (this.promiseStatus === Status.REJECTED) {
    resolvePromise(onRejected);
} else if (this.promiseStatus === Status.PENDING) {
    this.onFulFilledCallback.push(resolvePromise.bind(this, onFulFilled));
    this.onRejectedCallback.push(resolvePromise.bind(this, onRejected));
}
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

从这段代码中看到:

  1. onRejected函数会在resolvePromise中被捕获

  2. thenPromise执行reject(e),再抛出错误

  3. originPromiseconstructor捕获,对originPromise执行reject(e)

所以当前Promise的拒绝理由会被传给thenPromise作为thenPromise的拒绝理由,这也与《Promise A+》规范相符

这样做有什么好处呢?我们可以构建一个场景

ts
new MyPromise((resolve, reject)=>{
	resolve(1);
}).then(value=>{
    console.log(value);
    return value+1;
}).then(value=>{
    console.log(value);
    throw new Error('error');
}).then(value=>{
    console.log(value);
    return value+1;
}).then(value=>{
    console.log(value);
    return value+1;
}).catch(reason=>{
    console.log(reason);
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

.catch.then方法第二个回调函数的语法糖,在上面这种情况下我们通常不会在每一个.then中传递错误处理方法,这就太笨了,就像你对async/await进行错误捕获一样

ts
// 写法1
try{
	const a = await request1();
}catch(e){
	console.error(e);
}
try{
	const b = await request2();
}catch(e){
	console.error(e);
}
try{
	const c = await request3();
}catch(e){
	console.error(e);
}

// 写法2
try{
	const a = await request1();
    try{
        const b = await request2();
        try{
            const c = await request3();
        }catch(e){
            console.error(e);
        }
    }catch(e){
        console.error(e);
    }
}catch(e){
	console.error(e);
}

//写法3
try{
	const a = await request1();
    const b = await request2();
    const c = await request3();
}catch(e){
	console.error(e);
}
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

很显然写法3是最好的实现调用方式

这里的promise错误捕获同理,我们希望可以用一个.catch捕获链式调用中第一个拒绝理由,这就是promise的错误穿透或者叫异常穿透

最后,希望本章解析能够增强你对promise的理解!

Released under the MIT License.