真正的手写一次promise的实现,能更加深入理解运用promise。 俗话说的好,知其然须知其所以然,首先要了解promise的语法和使用。
promise是书写异步代码的一种方式,在ES6语法之前,异步操作一般通过回调函数实现,但是异步回调往往容易造成回调地狱,挨个嵌套的问题,promise应运而生,主要用于解决回调嵌问题,使得异步操作更为清晰。
promise是一个对象,存在三种状态 pedning(等待中), fulfilled(成功状态), rejected(失败状态)
基本语法:

1
2
3
4
5
6
7
8
9
const p = new Promise((resolve, reject) => {
// resolve 和 reject都是内部提供的
// resolve 是成功的时候需要调用的函数 将promise状态由 pending =>fulfilled
// reject 是失败的时候需要调用的函数 将promise状态由 pending =>rejected

// promise内部一般有一个异步操作 文件读写 ajax请求等==
// 异步操作 有成功 有失败
resolve(100)
})

使用promise

1
2
3
4
5
6
7
p.then(res => {
// 成功状态
console.log(res)
}).catch(err => {
// 失败状态
console.log(err)
})

因为p是由promise生成的一个对象,由此可以想到,通过创建类来实现promise

1
2
3
class Promise{
// 代码
}

下一步,先通过构造函数简单处理promise的参数,对于promise来说,传入的参数是一个函数,这个函数有包含两个函数的参数(resolve, reject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Promise{
// 构造函数
// constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。
constructor(fn) {
// fn中也有两个参数resolve reject 也是函数
let resolve = () => {
console.log('resolve,将状态由等待变成成功')
};
let reject = () => {
console.log('reject,将状态由等待变成失败')
}
// 此fn就相当于promise调用的整体的函数
// 执行fn
// 因为promise是立即执行函数
fn(resolve, reject)
}
}

// promise的参数是一个函数
const p = new Promise((resolve, reject) => {
resolve();
reject();
console.log('promise是立即执行的')
})

处理了函数参数,下一步整理状态,一开始梳理的promise得三个状态,在resolvereject中需要处理

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
class Promise{
// 构造函数
// constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。
constructor(fn) {
// 用this指向对象实例,保证每个promise对象实例的状态都是独立的
this.state = 'pending';
// 记录失败和错误的信息
this.msg = ''; // 成功信息
this.reason = ''; //失败原因
// fn中也有两个参数resolve reject 也是函数
let resolve = (val) => {
console.log('resolve,将状态由等待变成成功')
// promise的状态只能被改变一次
if (this.state === 'pending') {
this.state = 'fulfilled';
this.msg = val;
}
};
let reject = (val) => {
console.log('reject,将状态由等待变成失败')
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = val;
}
}
// 此fn就相当于promise调用的整体的函数
// 执行fn
// 因为promise是立即执行函数
fn(resolve, reject)

}

}

// promise的参数是一个函数
const p = new Promise((resolve, reject) => {
resolve(2000);
reject();
console.log('promise是立即执行的')
})
// {
// "state": "fulfilled",
// "msg": 2000,
// "reason": ""
// }
console.log(p)

接下来,处理.then即实例对象后面调用的then,是不是可以理解为then是原型对象promise下的一个方法,我们调用原型下的方法而已呢? so,在promiseclass中添加then方法。并且确定then方法的参数,按照es6中对promise的定义

Promise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。

其实对then来说有两个参数,一个是成功的回调,一个是失败的回调,有点类似一开始写的rejectresolve,先看一个正确的promise怎么调用then

1
2
3
4
5
6
7
p.then(res => {
// p成功之后的回调
console.log('成功')
}, err => {
// p失败之后的回调
console.log('失败')
})

来尝试给类中添加then方法

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
class Promise{
// 构造函数
// constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。
constructor(fn) {
// 用this指向对象实例,保证每个promise对象实例的状态都是独立的
this.state = 'pending';
// 记录失败和错误的信息
this.msg = ''; // 成功信息
this.reason = ''; //失败原因
// fn中也有两个参数resolve reject 也是函数
let resolve = (val) => {
console.log('resolve,将状态由等待变成成功')
// promise的状态只能被改变一次
if (this.state === 'pending') {
this.state = 'fulfilled';
this.msg = val;
}
};
let reject = (val) => {
console.log('reject,将状态由等待变成失败')
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = val;
}
}
// 此fn就相当于promise调用的整体的函数
// 执行fn
// 因为promise是立即执行函数
fn(resolve, reject)
}
// then作为原型对象的方法出现
then(fulfilledFn, rejectedFn) {
// then有两个函数参数
// 函数调用
// 判断状态 并传参
if (this.state === 'fulfilled') {
fulfilledFn(this.msg);
}
if (this.state === 'rejected') {
rejectedFn(this.reason)
}
}
}
// promise的参数是一个函数
const p = new Promise((resolve, reject) => {
resolve(2000);
reject();
console.log('promise是立即执行的')
})
p.then(res => {
// p成功之后的回调
console.log(res)
console.log('成功')
}, err => {
// p失败之后的回调
console.log(err)
console.log('失败')
})

考虑时序问题,看下面一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2000)
}, 1000)
console.log('promise是立即执行的')
})
p.then(res => {
// p成功之后的回调
console.log(res)
console.log('成功')
}, err => {
// p失败之后的回调
console.log(err)
console.log('失败')
})

查看控制台,发现打印的内容没有进入到then中,打印发现status还是pending状态,由于在promiseresolve是异步执行的,所以执行到then的时候,还没有完成status的状态变更,无法执行回调。
通过添加事件队列和判断处理事件推入队列时机(在then中但是状态是pending)和事件执行时机(改变status状态的两个函数)来处理resolvereject的异步事件

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
class Promise{
// 构造函数
// constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。
constructor(fn) {
// 用this指向对象实例,保证每个promise对象实例的状态都是独立的
this.state = 'pending';
// 记录失败和错误的信息
this.msg = ''; // 成功信息
this.reason = ''; //失败原因

// 存放then中的执行事件 任务队列
this.resolveArr = []; // 成功事件
this.rejectArr = []; // 失败事件

// fn中也有两个参数resolve reject 也是函数
let resolve = (val) => {
console.log('resolve,将状态由等待变成成功')
// promise的状态只能被改变一次
if (this.state === 'pending') {
this.state = 'fulfilled';
this.msg = val;
// 最终态之后调用执行代码
this.resolveArr.forEach((fn) => {
fn();
})
}
};
let reject = (val) => {
console.log('reject,将状态由等待变成失败')
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = val;
// 最终态之后调用执行代码
this.rejectArr.forEach((fn) => {
fn();
})
}
}
// 此fn就相当于promise调用的整体的函数
// 执行fn
// 因为promise是立即执行函数
fn(resolve, reject)
}
// then作为原型对象的方法出现
// fulfilledFn 成功调用的回调事件
// rejectedFn 失败调用的回调事件
then(fulfilledFn, rejectedFn) {
// then有两个函数参数
// 函数调用
// 判断状态 并传参
if (this.state === 'fulfilled') {
fulfilledFn(this.msg);
}
if (this.state === 'rejected') {
rejectedFn(this.reason)
}
// 处理异步情况
// 状态还是pending 无法直接处理
// 把要执行的事件放到队列里
if (this.state === 'pending') {
this.resolveArr.push(() => {
fulfilledFn(this.msg);
});
this.rejectArr.push(() => {
rejectedFn(this.reason);
})
}
}
}
// promise的参数是一个函数
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(666)
}, 1000)
console.log('promise是立即执行的')
})
p.then(res => {
// p成功之后的回调
console.log(res)
console.log('成功')
}, err => {
// p失败之后的回调
console.log(err)
console.log('失败')
})
console.log(p)

处理完异步,接下来看一下promise的链式调用

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

按照promise的定义,改造我们的class,让then再返回promise实现链式,这里需要注意的是,当then里面去判断返回的是不是当前promise本身,因为代码执行顺序关系,会报错,因为当时的promise还没生成完毕,所以在判断过程中,添加了异步操作

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
class Promise{
// 构造函数
// constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。
constructor(fn) {
// 用this指向对象实例,保证每个promise对象实例的状态都是独立的
this.state = 'pending';
// 记录失败和错误的信息
this.msg = ''; // 成功信息
this.reason = ''; //失败原因

// 存放then中的执行事件 任务队列
this.resolveArr = []; // 成功事件
this.rejectArr = []; // 失败事件

// fn中也有两个参数resolve reject 也是函数
let resolve = (val) => {
// console.log('resolve,将状态由等待变成成功')
// promise的状态只能被改变一次
if (this.state === 'pending') {
this.state = 'fulfilled';
this.msg = val;
// 最终态之后调用执行代码
this.resolveArr.forEach((fn) => {
fn();
})
}
};
let reject = (val) => {
console.log('reject,将状态由等待变成失败')
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = val;
// 最终态之后调用执行代码
this.rejectArr.forEach((fn) => {
fn();
})
}

}
// 此fn就相当于promise调用的整体的函数
// 执行fn
// 因为promise是立即执行函数
fn(resolve, reject)

}
// then作为原型对象的方法出现
then(fulfilledFn, rejectedFn) {
// then有两个函数参数
// 函数调用
// 判断状态 并传参
// 新建一个promise
let promise2 = new Promise((resolve, reject) => {
// p1立即执行
if (this.state === 'fulfilled') {
// 判断返回值类型
setTimeout(() => {
// 拿到回调函数的返回值
let fulfilledRes = fulfilledFn(this.msg);
// 判断返回的fulfilledRes是不是promise2
if (fulfilledRes === promise2) {
// 记得报错
console.error('循环调用了promise,报错')
} else {
if (fulfilledRes instanceof Promise) {
fulfilledRes.then((msg) => {
resolve(msg)
}, (err) => {
reject(err)
})
} else {
resolve(fulfilledRes)
}
}
}, 0)
}
if (this.state === 'rejected') {
setTimeout(() => {
// 拿到回调函数的返回值
let rejectedRes = rejectedFn(this.msg);
// Todo...
if (fulfilledRes === promise2) {
console.log('循环了')
} else {
if (rejectedRes instanceof Promise) {
rejectedRes.then((msg) => {
resolve(msg)
}, (err) => {
reject(err)
})
} else {
reject(rejectedRes)
}
}
}, 0)
}
// 处理异步情况
// 状态还是pending 无法直接处理
// 把要执行的事件放到队列里
if (this.state === 'pending') {
this.resolveArr.push(() => {
fulfilledFn(this.msg);
});
this.rejectArr.push(() => {
rejectedFn(this.reason);
})
}

})
// 在then中返回一个promise 此promise不能和p1相同
return promise2;
}

}
let p = new Promise((resolve, reject) => {
resolve(300)
});
const p2 = p.then((res) => {
console.log(res)
return p2
})
p2.then(res => {
console.log(res)
})

promise类有了,接下去,实现promise的静态方法
resolve

有时需要将现有对象转为 Promise 对象,Promise.resolve()方法就起到这个作用。

1
2
3
4
5
Promise.resolve = (val) => {
return new Promise((resolve, reject) => {
resolve(val);
})
}

reject

Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected

1
2
3
4
5
Promise.reject = (val) => {
return new Promise((resolve, reject) => {
reject(val);
})
}

粗糙版本的promise差不多已经实现了,还有很多方法没有挨个实现,代码也不是很严谨,这次手写promise的过程,一点是让我重新温习巩固了关于promise的基础知识和用法,还有一点就是手写一个已有的语法应该如何逐步去实现,逐步验证,从大范围到小细节,就好比画画,先模仿,写个大概(先写了一个class,可以new对象),再逐步修饰(添加status,添加各种需要的参数)。总结下来是一次不错的经验。