Promise

一、最简单Promnise

根据定义可知,promise是一个规范,规范并不在意promise是怎样createrejectfulfill。规范只在意有木有一个then方法。
根据我们下面的使用方式

1
2
3
4
5
6
7
8
9
let p = new MyPromise(function (resolve, reject) {
setTimeout(() => {
resolve(100);
}, 5000);
});

p.then(function (data) {
console.log(data);
});

可以清楚的感知到,我们传入了个函数,这个函数会在内部执行。同时,then方法会注册一个回调,该回调在resolve后进行执行,并且拿到resolve的传参。那么,我们的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
function MyPromise(executor) {

let self = this;

self.value = undefined;
self.reason = undefined;

self.onFulfilled = null;
self.onRejected = null;

function resolve(value) {
self.value = value;
self.onFulfilled(value);
}

function reject(reason) {
self.reason = reason;
self.onRejected(reason);
}

try {
executor(resolve, reject);
} catch (e) {
console.log(e);
}
}

MyPromise.prototype.then = function (onFulfilled, onRejected) {
this.onFulfilled = onFulfilled;
this.onRejected = onRejected;
}



let p = new MyPromise(function (resolve, reject) {
setTimeout(() => {
resolve(100);
}, 5000);
});

p.then(function (data) {
console.log(data);
});

二、不异步行不行

要知道我们正常使用Promise传入的函数,可以是异步函数,可以是同步函数。但是,当我们试着给setTimeout干掉之后,会发现报错。

1
2
3
4
5
6
7
let p = new MyPromise(function (resolve, reject) {
resolve(100);
});

p.then(function (data) {
console.log(data);
});

具体的错误发生在上面的resolve定义的地方

1
2
3
4
function resolve(value) {
self.value = value;
self.onFulfilled(value);
}

因为resolve执行的时候,then注册的回调函数还没有挂载在self.onFulfilled上。解决办法很简单,只需将resolve方法的主体,使用setTimeout包裹即可。reject同理。
则上面的最简单的MyPromise就变成了:

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
function MyPromise(executor) {

let self = this;

self.value = undefined;
self.reason = undefined;

self.onFulfilled = null;
self.onRejected = null;

function resolve(value) {
// 包个setTimeout
setTimeout(() => {
self.value = value;
self.onFulfilled(value);
});
}

function reject(reason) {
// 包个setTimeout
setTimeout(() => {
self.reason = reason;
self.onRejected(reason);
});
}

try {
executor(resolve, reject);
} catch (e) {
console.log(e);
}
}

MyPromise.prototype.then = function (onFulfilled, onRejected) {
this.onFulfilled = onFulfilled;
this.onRejected = onRejected;
}

let p = new MyPromise(function (resolve, reject) {
resolve(100);
});
p.then(function (data) {
console.log(data);
});

三、状态

我们都知道,每个promise具有3个状态。pendingfulfilledrejected。在pending状态下可以分别向其他状态转换,而且fulfilledrejected的状态不能改变。那么,MyPromise可以如下表示

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
function MyPromise(executor) {

let self = this;

self.value = undefined;
self.reason = undefined;
// 加个状态
self.status = 'pending';
self.onFulfilled = null;
self.onRejected = null;

function resolve(value) {
if (self.status === 'pending') {
setTimeout(() => {
// 改变状态
self.status = 'fulfilled';
self.value = value;
self.onFulfilled(value);
});
}
}

function reject(reason) {
if (self.status === 'pending') {
setTimeout(() => {
// 改变状态
self.status = 'rejected';
self.reason = reason;
self.onRejected(reason);
});
}
}

try {
executor(resolve, reject);
} catch (e) {
console.log(e);
}
}

MyPromise.prototype.then = function (onFulfilled, onRejected) {
if (this.status === 'pending') {
// 如果是pending,则注册
this.onFulfilled = onFulfilled;
this.onRejected = onRejected;
} else if (this.status === 'fulfilled') {
// 如果是fulfilled则直接执行
onFulfilled();
} else if (this.status === 'rejected') {
// 如果是rejected则直接执行
onRejected();
}
}

let p = new MyPromise(function (resolve, reject) {
resolve(100);
});
p.then(function (data) {
console.log(data);
});

四、链式操作

目前来看,只能使用一个then,要想使用promise.then(func1).then(func2).then(func3)这种操作,还需要对回调函数的注册进行一番改造。操作起来非常简单,只需在全局注册2个数组用来存放回调函数,在pending时push,在resolve时遍历回调数组,并挨个调用即可。这里需要谨记,在函数主体(后面简称main)执行的时候,then接收的参数,理论上应该是一个函数,如果不是函数,后续我们的MyPromise会做一些容错处理。main执行到then,会将then内的参数(函数)挂起,到下一轮事件循环时候再去执行。对这里不太清楚的小伙伴可以了解下microtaskmacrotask

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
function MyPromise(executor) {

let self = this;

self.value = undefined;
self.reason = undefined;
self.status = 'pending';
// 变成数组
self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = [];

function resolve(value) {
if (self.status === 'pending') {
setTimeout(() => {
self.status = 'fulfilled';
self.value = value;
// 遍历回调数组,并调用
self.onFulfilledCallbacks.forEach(callback => {
callback(self.value);
});
});
}
}

function reject(reason) {
if (self.status === 'pending') {
setTimeout(() => {
self.status = 'rejected';
self.reason = reason;
// 遍历循环数组,并调用
self.onRejectedCallbacks.forEach(callback => {
callback(self.value);
});
});
}
}

try {
executor(resolve, reject);
} catch (e) {
console.log(e);
}
}

MyPromise.prototype.then = function (onFulfilled, onRejected) {
if (this.status === 'pending') {
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
} else if (this.status === 'fulfilled') {
onFulfilled();
} else if (this.status === 'rejected') {
onRejected();
}
// 返回自己,才能链式操作
return this;
}

let p = new MyPromise(function (resolve, reject) {
resolve(100);
});
p.then(function (data) {
console.log(data);
}).then(function (data) {
console.log(data);
});

上述代码看起来没啥问题,但是在诸如以下使用时,

1
2
3
4
5
6
7
8
9
10
let p = new MyPromise(function (resolve, reject) {
resolve(100);
});
p.then(function (data) {
setTimeout(() => {
console.log(data + 1);
}, 100);
}).then(function (data) {
console.log(data);
});

会打印100 101。也就是说,第二个then先执行了。而且data的值,由于读的是同一个promise实例的data,在resolve的时候,也写死了,没有办法继续透传改变。要想实现每一个then都读上一个thenvalue,我们立刻可以想到,then应该返回一个新的promise,他拥有自己的data,这样一级一级往下传递,就实现了真正的异步串行操作。

五、then的改造

回到上一节最后抛出的问题,上面的所有步骤,then中都直接返回了this,但是,问题来了,我们的需求为:每次调用then传参,取决于上一个then回调的返回值。看下面片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
MyPromise.prototype.then = function (onFulfilled, onRejected) {
let self = this;
let promise2;

if (self.status === 'fulfilled') {
return promise2 = new MyPromise(function (resolve, reject) {

})
} else if (self.status === 'rejected') {
return promise2 = new MyPromise(function (resolve, reject) {

})
} else if (self.status === 'pending') {
return promise2 = new MyPromise(function (resolve, reject) {

})
}
}

可以看到,then中当前(对,当前,看清楚,我是说当前)的promise有3个状态,我们分别返回一个新的(新的,我new了,new了之后,下一级then内的this会变)promise

另外考虑如下使用方式:

1
2
3
4
5
6
7
8
let p = new MyPromise(function (resolve, reject) {
resolve(100);
});
p.then(function (data) {
return data + 200
}).then(function (data) {
console.log(data);
});

我们希望最后的console.log(data)的结果是300,那么,在then的内部,promise2应该先拿到上一个MyPromise的值(也就是onFulfilled或者onRejected的值),然后马上resolve掉这个内部的promise。注意,这个resolve是为了传递内部的promise,也就是promise2的值,所以不管什么情况,最后只管resolve就是了。

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
MyPromise.prototype.then = function (onFulfilled, onRejected) {
let self = this;
let promise2;

if (self.status === 'fulfilled') {
return promise2 = new MyPromise(function (resolve, reject) {
try {
let x = onFulfilled(self.data);
resolve(x);
} catch (e) {
reject(e);
}
});
} else if (self.status === 'rejected') {
return promise2 = new MyPromise(function (resolve, reject) {
try {
let x = onRejected(self.data);
resolve(x);
} catch (e) {
reject(e)
}
});
} else if (self.status === 'pending') {
return promise2 = new MyPromise(function (resolve, reject) {
// 如果是`pending`状态,则一样只需给当前的数组压入回调即可
self.onFulfilledCallbacks.push((value) => {
try {
let x = onFulfilled(value);
resolve(x);
} catch (e) {
reject(e);
}
});
self.onRejectedCallbacks.push((reason) => {
try {
let x = onRejected(reason);
resolve(x);
} catch (e) {
reject(e);
}
});
});
}
}

到目前为止,看似都很美好。但是如果onFulfilled返回的x是一个新的MyPromise呢?所以,在Promise2中,不能简单的resolve这个promise2。我们这里定义个函数,用来专门处理x的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 这里传入4个参数,x为promise2的onFulfilled或者onRejected的值,resolve和reject就是promise2的2个实参,至于为啥传递个`promise2`进来,后面我们慢慢完善,就发现他的作用了,这里暂时不管。
function resolvePromise(promise2, x, resolve, reject) {
if (x instanceof MyPromise) {
// 如果x是promise的实例,则需要分类讨论是否pending
if (x.status === 'pending') {
// 如果promise仍然是pending,则注册这个promise的回调函数,继续递归,直到返回值不是pending的promise为止
x.then(value => {
resolvePromise(promise2, value, resolve, reject);
}, reject);
} else {
// 如果promise以及被决断,则肯定有个resolve或者reject的值,直接调用then,拿到promise的最终值即可
x.then(resolve, reject);
}
} else {
// 如果x不是promise,直接resolve,降级为上面的x值非promise的情况
resolve(x);
}
}

这个函数是整个MyPromise中最抽象的地方。我们先分类讨论它的情况。

  • 如果x的值不是MyPromise的实例,问题降级为上面xpromise的情况。
  • 如果x的值是MyPromise的实例,但是状态已经被fulfilled或者rejected,则x肯定有一个最终的value,我们只需继续x.then(reolve, reject),拿到x这个MyPromise的最终值即可。
  • 如果x的值是MyPromise的实例,但是状态仍然是pending,我们同样需要调用then函数(promise每一步只能去then),去注册这个x的回调函数(再次啰嗦,xMyPromise实例),继续递归,直到返回值不是pending状态的MyPromise为止。

至此,我们的MyPromise基本可用了,贴一下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
MyPromise.prototype.then = function (onFulfilled, onRejected) {
let self = this;
let promise2;

if (self.status === 'fulfilled') {
return promise2 = new MyPromise(function (resolve, reject) {
try {
let x = onFulfilled(self.data);
// 替换
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
} else if (self.status === 'rejected') {
return promise2 = new MyPromise(function (resolve, reject) {
try {
let x = onRejected(self.data);
// 替换
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
});
} else if (self.status === 'pending') {
return promise2 = new MyPromise(function (resolve, reject) {
// 如果是`pending`状态,则一样只需给当前的数组压入回调即可
self.onFulfilledCallbacks.push((value) => {
try {
let x = onFulfilled(value);
// 替换
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
self.onRejectedCallbacks.push((reason) => {
try {
let x = onRejected(reason);
// 替换
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
}

组装完成后,配合下面的demo,可以查看

1
2
3
4
5
6
7
8
9
10
11
12
let p = new MyPromise(function (resolve, reject) {
resolve(100);
});
p.then(function (data) {
return new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(data + 100);
}, 2000);
});
}).then(function (data) {
console.log(data);
});

基本的Promise以及实现了,但是仍然有很多细节,我们慢慢补充。