手写一个Promise

最近在掘金上看了一些关于promise的文章,然后学习了一番,发现是可以自己手写实现一套promise的,于是做了一番学习,现在来整理一下。

Promise状态

promise是有一套规范的,只要实现的promise符合这套规范,就说明显示的promise是可以正常使用的。这套规范的地址是 https://promisesaplus.com/ 。上面除了有规范,还给出了测试工具,方便测试代码。

promise的规范写的很详细,包括用语,各种情况下promise应该有的处理方式。这个就开始实现吧。

规范的一开始就明确定义了(1.1)promise是一个带有then方法的对象或者函数,所以我们实现的promise需要有一个then方法;(2.1)promise有三个状态,pending等待态,fulfilled成功态,rejected失败态,而关于这三种状态关系,在(2.1)中有详细的说明:

  1. 等待态可以转化为成功态或失败态;
  2. 成功态不能转化为其他状态,而且需要有一个value
  3. 失败态不能转化为其他状态,而且需要有一个reason
    (valuereason值不可变,但如果指向是对象的话则指向不可变)

基于以上规范,promise需要有一个状态state和对应成功态的value跟失败态的reason:

1
2
3
4
5
6
7
8
9
10
11
12
13
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

class Promise {
constructor (executor) {
this.state = PENDING;
this.value = undefined;
this.reason = undefined;
// ...
}
// ...
}

Promise executor

上面代码中constructor传入了一个executor,这个是按照原生Promise的写法,声明一个Promise对象实例需要传入executor,这个executor需要带有两个参数,包括resolvereject

resolvereject是用来判断promise状态的两个函数,要求Promise转化为成功态时要调用resolve,并传入value,而转化为失败态时要调用reject,并传入reason。而且,executor是个立即执行的,是同步的。

resolve获得value时,会把值赋给promise的value,并判断当前是否为等待态,是则会把状态改为成功态;而reject操作类似,把获得的reason赋值给promise的reason,然后判断等待态后把状态改为失败态。

executor的执行可能出现运行报错,所以需要用try catch来捕获报错,捕获的报错则传给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
25
26
27
28
29
class Promise {
constructor (executor) {
this.state = PENDING;
this.value = undefined;
this.reason = undefined;
let resolve = (value) => {
// ...
if (this.state === PENDING) {
this.state = FULFILLED;
this.value = value;
this.onFulfilledStack.forEach(fn => fn());
}
};
let reject = (reason) => {
if (this.state === PENDING) {
this.state = REJECTED;
this.reason = reason;
this.onRejectedStack.forEach(fn => fn());
}
};

try {
executor(resolve, reject);
} catch (exception) {
reject(exception);
}
}
// ...
}

Promise then

promise/A+规范要求实现的promise需要有then方法。这个方法需要传入两个回调函数,onFulfilledonRejected。因为支持链式调用,所以也要求函数返回的是一个promise。

首先,对于以上的要求,可以写出下面then方法的结构:

1
2
3
4
5
6
7
8
9
10
11
class Promise {
// constructor...
then (onFulfilled, onRejected) {
// ...
let promise2;
promise2 = new Promise((resolve, reject) => {
// ...
});
return promise2;
}
}

then方法中的onFulfilledonRejectedpromise2规范中有比较详细的要求,所以我们单独的实现。

onFulfilledonRejected调用要求

首先,只有当promise的状态为成功态时,才可以调用onFulfilled回调,并传入value,而只有为失败态时,才可以调用onRejected回调,并传入reason,而等待态的情况下,两个回调均不执行,会等待到状态转化之后才会根据状态执行。

同时,由于then方法支持多次调用,也就是说在promise等待态时,then方法调用的情况,所以需要在promise内部使用两个数组来储存所有的onFulfilledonRejected方法,并在转化状态后按照先后顺序调用执行。

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
// Promise constructor
this.onFulfilledStack = [];
this.onRejectedStack = [];
let resolve = (value) => {
if (this.state === PENDING) {
this.state = FULFILLED;
this.value = value;
this.onFulfilledStack.forEach(fn => fn());
}
};
let reject = (reason) => {
if (this.state === PENDING) {
this.state = REJECTED;
this.reason = reason;
this.onRejectedStack.forEach(fn => fn());
}
};

// then的promise2内部
if (this.state === FULFILLED) {
onFulfilled(this.value);
}
if (this.state === REJECTED) {
onRejected(this.reason);
}
if (this.state === PENDING) {
this.onFulfilledStack.push(() => {
onFulfilled(this.value);
})
this.onRejectedStack.push(() => {
onRejected(this.reason);
});

onFulfilledonRejected结果判断

由于Promise链式调用,在then中执行onFulfilledonRejected返回的值有可能也是一个Promise对象,也有可能是一个其他普通的值,还有可能是抛出一个错误,而这些值都有各自不同的处理要求:

  1. 如果返回的值是普通值,则调用promise2的resolve;
  2. 如果返回的值是一个promise,则需要等待执行完把这个promise的valuereason传给promise2的resolvereject
  3. 如果是抛出一个错误,则调用promise2的reject;
  4. 如果返回的值等于promise2,则抛出类型错误;

基于上面的要求,可以使用一个resolvePromise来处理,需要传入以下值:

  1. 执行onFulfilledonRejected之后返回的值,x,用来判断处理结果;
  2. promise2的resolvereject,用来接收x;
  3. promise2,用来判断x是否与之引用相等。
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
promise2 = new Promise((resolve, reject) => {
if (this.status === SUCCESS) {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
}
if (this.status === FAIL) {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
}
if (this.status === PENDING) {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
});
}
})

返回值x的判断

Promise A+规范提供了对x的判断方案。首先,判断x是否为一个方法或者一个对象,因为Promise的实现可以通过FunctionObject来实现。其次,x带有一个then方法,这是本身Promise规范要求
的方法。除此之外,都判断为普通值。

另外,如果x是一个Promise,那么作为x成功态返回的值,同样也要判断是否为Promise,这个判断的处理跟x的处理相同,所以resolvePromise可以以递归方法的形式实现。

在判断完x为Promise之后,Promise A+规范有一个比较严谨的考虑,就是为了防止同时出现成功态跟失败态,要在调用promise2的resolvereject之后,禁止再调用promise2的rejectresolve

因此,resolvePromise实现如下:

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
function resolvePromise (promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<my Promise>'))
}
let called;
if (typeof x === 'function' || (typeof x === 'object' && x != null)) {
try {
let then = x.then
if (typeof then === 'function') {
then.call(x, y => {
if (called) return
called = true
resolvePromise (promise2, y, resolve, reject)
}, r => {
if (called) return // 2)
called = true
reject(r)
})
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
}

测试

以上基本实现基于Promise A+规范的Promise类。最后可以安装promise-aplus-tasts库对实现的Promise做检验。测试之前需要加以下代码:

1
2
3
4
5
6
7
8
Promise.defer = Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}

这段代码本身也实现了一种异步操作代码同步化的处理。最后使用命令promises-aplus-tests xxx.js执行测试。