学习Javascript之模拟实现Promise

文章目录
  1. 1. 前言
  2. 2. 正文
    1. 2.1. 初始化构造函数
    2. 2.2. 异步
    3. 2.3. 异常处理
    4. 2.4. 静态方法
    5. 2.5. 完整实现

前言

本文1683字,阅读大约需要5分钟。

总括: 本文使用低版本Javascript模拟实现了Promise对象。

  • 参考文档:PromisePromises/A+
  • 公众号:「前端进阶学习」,回复「666」,获取一揽子前端技术书籍

盛年不重来,一日难再晨。

正文

不熟悉Promise语法的同学可以去MDN的Promise看下语法。本文旨在使用ES6版本以下的语法去模拟实现一个Promise,规范参考Promises/A+,中文版Promises/A+

初始化构造函数

实现基本的Promise构造函数,首先要具有以下属性和方法:

  1. 当前Promise状态;
  2. 当前的传递值;
  3. 原型方法then;
  4. 原型方法catch

初始化后第一版代码如下:

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
/**
* @param {function} executor
* executor 形如 function(resolve, reject) {}
*/
function Promise(executor) {
// 当前promise值
this.value = null;
// 存储回调函数
this.callbacks = [];
// 当前promise状态例
this.status = 'pending';
var _this = this;
function resolve(initValue) {
_this.value = initValue;
_this.status = 'resolved';
}
function reject(initValue) {
_this.value = initValue;
_this.status = 'rejected';
}
executor(resolve);
}

Promise.prototype.then = function(onFulfilled, onRejected) {
if(this.status === 'resolved') return this;
return this;
}
Promise.prototype.catch = function(onRejected) {
if(this.status === 'rejected') return this;
return this.then(null, onRejected);
}

解释下上面代码,我们声明了一个Promise构造函数。然后在内部声明了resolve函数和reject函数,在函数内更改状态,然后将其传给Promise的参数executor。this.value储存当前传给回调函数的值,this.callbacks数组存储的是回调函数,因为一个同一个promise实例可能会多次调用then方法this.status存储当前的promise状态,一旦状态更改再调用方法立即返回,状态不可再更改。方法then返回this从而支持链式调用。我们要实现的第一个特性就是异步,即Promise实例的then方法中的回调函数要异步执行。

异步

ES6新引入了微任务队列的概念,比如Promise实例中then的回调函数就是在微任务队列中等待执行,在低版本JS中我们可以使用setTimeout来模拟微任务队列。另外thencatch是可以链式调用的,而且resolve的参数要传到then的回调函数中,reject的参数要传到catch的回调函数中。而这里出现一个问题,thencatch方法都是同步调用的,而回调函数需要异步调用,这就意味着我们不能直接在then方法中直接将回调调用执行。所以我们需要将回调函数保存起来,延迟调用。另外标准里then方法返回的是一个promise实例,并不是this。综合以上叙述,我们需要实现的特性是:

  1. then返回一个promise实例,该实例处于resolved状态(标准要求);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Promise.prototype.then = function(onFulfilled, onRejected) {
var _this = this;
return new Promise(function(resolve, reject) {
if (_this.status === 'pending') {
var defer = {
// 当前promise实例的回调存储
onFulfilled: onFulfilled || null,
onRejected: onRejected || null,
// 下一个promise实例的resolve和reject函数
resolve: resolve || null,
reject: reject || null
};
_this.defers.push(defer);
return;
}
// 状态一旦更改直接返回将上一个then的回调函数返回值保存,传给下一个实例
var ret = onFulfilled(_this.value);
resolve(ret);
});
}

解释下上面的代码,then方法返回了一个promise实例,通过在defer中保存当前then的回调函数和下一个实例的resolve,reject函数,将当前的promise实例和下一个promise实例连接在一起。根本原因在于我们的then方法,catch方法都是同步调用,但回调函数是异步调用,所以我们需要将当前实例的回调函数和下一个实例的resolve,reject函数保存起来,从而保证链式调用,值能传到下一个promise实例。

  1. 回调函数延迟异步调用;
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
function asyncExec(self) {
setTimeout(function() {
// 将所有回调执行
this.defers.forEach(function(defer) {
var callback = self.status === 'resolved' ? defer.onFulfilled : defer.onRejected;
// 如果then回调函数为空,则执行实例的resolve或reject函数
if (callback === null) {
callback = self.status === 'resolved' ? defer.resolve : defer.reject;
callback(self.value);
return;
}
// 异步执行then回调函数
var ret = callback(self.value);
// 将值传给下一个promise实例
defer.resolve(ret);
});
}.bind(self));
}
var _this = this;
function resolve(initValue) {
_this.value = initValue;
_this.status = 'resolved';
asyncExec(_this);
}
function reject(initValue) {
_this.value = initValue;
_this.status = 'rejected';
asyncExec(_this);
}

统一封装asyncExec方法,将所有的回调函数进行执行,此时promise的状态一定已经更改,通过判断状态来决定执行成功回调还是失败的回调。如果then方法没有传入参数则执行then返回的promise实例中的resolve或是reject函数。

异常处理

在JS中回调函数执行的异常可以使用try/catch捕获错误。

1
2
3
4
5
6
7
8
try {
// 异步执行then回调函数
var ret = callback(self.value);
// 将值传给下一个promise实例
defer.resolve(ret);
} catch(e) {
defer.reject(e);
}

上面代码通过try/catch捕获执行中的错误,然后将错误传到下一个实例(then方法返回的实例)的reject函数中。

静态方法

Promise构造函数除了原型方法之外,在Promise函数上改挂在着resolve方法和reject方法,方便调用:

1
2
3
4
5
6
7
8
9
10
PromiseNew.resolve = function(value) {
return new PromiseNew(function(resolve) {
resolve(value);
});
}
PromiseNew.reject = function(value) {
return new PromiseNew(function(_, reject) {
reject(value);
});
}

完整实现

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
/**
* @param {function} executor
* 形如 function(resolve, reject) {}
*/
function Promise(executor) {
// 当前promise值
this.value = null;
// 存储回调函数
this.defers = [];
// 当前promise状态例
this.status = 'pending';
var _this = this;
function resolve(initValue) {
_this.value = initValue;
_this.status = 'resolved';
asyncExec(_this);
}
function reject(initValue) {
_this.value = initValue;
_this.status = 'rejected';
asyncExec(_this);
}
function asyncExec(self) {
setTimeout(function() {
// 将所有回调执行
// console.warn(this.defers);
this.defers.forEach(function(defer) {
// 一旦状态更改,直接返回回调函数
var callback = self.status === 'resolved' ? defer.onFulfilled : defer.onRejected;
if (callback === null) {
callback = self.status === 'resolved' ? defer.resolve : defer.reject;
callback(self.value);
return;
}
try {
// 异步执行then回调函数
var ret = callback(self.value);
// 将值传给下一个promise实例
defer.resolve(ret);
} catch(e) {
defer.reject(e);
}
});
}.bind(self));
}
executor(resolve, reject);
}

Promise.prototype.then = function(onFulfilled, onRejected) {
var _this = this;
return new Promise(function(resolve, reject) {
if (_this.status === 'pending') {
_this.defers.push({
// 当前promise实例的回调存储
onFulfilled: onFulfilled || null,
onRejected: onRejected || null,
// 下一个promise实例的resolve和reject函数
resolve: resolve || null,
reject: reject || null
});
return;
}
// 状态一旦更改直接返回新的promise状态
try {
// 异步执行then回调函数
var ret = onFulfilled(self.value);
// 将值传给下一个promise实例
resolve(ret);
} catch(e) {
reject(e);
}
});
}

Promise.prototype.catch = function(onRejected) {
return this.then(null, onRejected);
}

Promise.resolve = function(value) {
return new Promise(function(resolve) {
resolve(value);
});
}
Promise.reject = function(value) {
return new Promise(function(_, reject) {
reject(value);
});
}

以上。


能力有限,水平一般,欢迎勘误,不胜感激。

转载请获本人授权,并注明作者和出处。

订阅更多文章可关注公众号「前端进阶学习」,回复「666」,获取一揽子前端技术书籍

前端进阶学习

如果您觉得我的文章对您有用,请随意打赏。

您的支持将鼓励我继续创作!

人过留名,雁过留声
听听你的声音

回复 Username 留言: content x