手写Promise,通过Promise/A+的872个测试
OUDUIDUI

github

Promise的声明

当我们使用Promise的时候,通常都是new Promise((resolve, reject) => {})

因此我们可以看出:

  • Promise是一个类;
  • Promise类的构造函数的第一个参数是函数,这个函数叫处理器函数(executor function);
  • 而在处理器函数中,有了两个参数:resolvereject
    • 当异步任务顺利完成且返回结果值的时候,我们会调用resolve函数;
    • 当异步任务失败且返回失败原因(通常是一个错误对象)时,会调用reject函数。

因此,我们可以初步声明一下Promise类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Promise {
/**
* 构造器
* @returns {Promise<object>}
* @param executor<function>: executor有两个参数:resolve和reject
*/
constructor(executor) {
// resolve 成功
const resolve = () => {};

// reject 失败
const reject = () => {};

// 执行 executor
executor(resolve,reject);
}
}

实现Promise的基本状态

Promise存在着三种状态:pending(等待态)、fulfilled(成功态)和rejected(失败态):

  • Promise的初始状态是pending状态;
  • pending状态可以转换为fulfilled状态和rejected状态;
  • fulfilled状态不可以转为其他状态,且必须有一个不可改变的值(value);
  • rejected状态不可以转为其他状态,且必须有一个不可改变的原因(reason);
  • 当在处理器函数中调用resolve函数并传入参数value,则状态改变为fulfilled,且不可以改变;
  • 当在处理器函数中调用reject函数并传入参数reason,则状态改变为rejected,且不可以改变;
  • 若处理器函数执行中报错,直接执行reject函数。

因此,我们需要在Promise类中设置三个变量:state(状态变量),value(成功值的变量)和reason(失败原因的变量),然后在resolve函数、reject函数以及执行executor函数报错的时候改变state的值。

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
class Promise {
constructor(executor) {
// 初始化状态
this.state = 'pending';
// 成功的值
this.value = undefined;
// 失败的原因
this.reason = undefined;

/**
* resolve 成功函数
* @param value<any>: 成功的值
*/
const resolve = (value) => {
// 只能在状态为pending的时候执行
if(this.state === 'pending'){
// resolve调用后,state转化为fulfilled
this.state = 'fulfilled';
// 存储value
this.value = value;
}
};

/**
* reject 失败函数
* @param reason<any>: 失败的原因
*/
const reject = (reason) => {
// 只能在状态为pending的时候执行
if(this.state === 'pending'){
// resolve调用后,state转化为rejected
this.state = 'rejected';
// 存储reason
this.reason = reason;
}
};

// 如果executor执行报错,直接执行reject()
try {
executor(resolve,reject);
}catch (e){
reject(e);
}
}
}

then方法

Promise有一个then方法,而该方法中有两个参数:onFulfilledonRejected

  • 这两个参数都是一个函数,且会返回一个结果值;
  • 当状态为fulfilled,只执行onFulfilled,传入this.value
  • 当状态为rejected,只执行onRejected,传入this.reason

因此我们可以来实现一下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
class Promise {
constructor(executor) {...}

/**
* then 方法
* @param onFulfilled<function>: 状态为fulfilled时调用
* @param onRejected<function>: 状态为rejected时调用
*/
then(onFulfilled, onRejected) {
// 状态为fulfilled的时候,执行onFulfilled,并传入this.value
if(this.state === 'fulfilled'){
/**
* onFulfilled 方法
* @param value<function>: 成功的结果
*/
onFulfilled(this.value)
}

// 状态为rejected的时候,onRejected,并传入this.reason
if(this.state === 'rejected'){
/**
* onRejected 方法
* @param reason<function>: 失败的原因
*/
onRejected(this.reason)
}
}
}

异步实现

Promise实际上一个异步操作:

  • resolve()是在setTimeout内执行的;
  • 当执行then()函数时,如果状态是pending时,我们需要等待状态结束后,才继续执行,因此此时我们需要将then()的两个参数onFulfilledonRejected存起来;
  • 因为一个Promise实例可以调用多次then(),因此我们需要将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
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
class Promise {
/**
* 构造器
* @returns {Promise<object>}
* @param executor<function>: executor有两个参数:resolve和reject
*/
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
// 存储onFulfilled的数组
this.onResolvedCallbacks = [];
// 存储onRejected的数组
this.onRejectedCallbacks = [];

const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
// 一旦resolve执行,调用onResolvedCallbacks数组的函数
this.onResolvedCallbacks.forEach(fn => fn());
}
};

const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
// 一旦reject执行,调用onRejectedCallbacks数组的函数
this.onRejectedCallbacks.forEach(fn=>fn());
}
};

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

then(onFulfilled, onRejected) {
if (this.state === 'fulfilled') {
onFulfilled(this.value)
}


if (this.state === 'rejected') {
onRejected(this.reason)
}

// 状态为pending的时候,将onFulfilled、onRejected存入数组
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
onFulfilled(this.value)
})
this.onRejectedCallbacks.push(() => {
onRejected(this.reason)
})
}
}
}

实现链式调用

我们常常会像下面代码一样使用Promise

1
2
3
4
new Promise()
.then()
.then()
.then()

这种方法叫做链式调用,通常是用来解决回调地狱(Callback Hell)的,就如下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
fs.readdir(source, function (err, files) {
if (err) {
console.log('Error finding files: ' + err)
} else {
files.forEach(function (filename, fileIndex) {
console.log(filename)
gm(source + filename).size(function (err, values) {
if (err) {
console.log('Error identifying file size: ' + err)
} else {
console.log(filename + ' : ' + values)
aspect = (values.width / values.height)
widths.forEach(function (width, widthIndex) {
height = Math.round(width / aspect)
console.log('resizing ' + filename + 'to ' + height + 'x' + height)
this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
if (err) console.log('Error writing file: ' + err)
})
}.bind(this))
}
})
})
}
})

为了实现链式调用,我们需要满足一下几点:

  • 我们需要在then()返回一个新的Promise实例;
  • 如果上一个then()返回了一个值,则这个值就是onFulfilled()或者onRejected()的值,我们需要把这个值传递到下一个then()中。

而对于上一个then()的返回值,我们需要对齐进行一定的处理,因此封装一个resolvePromise()的方法去进行判断处理;

接下来我们对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
class Promise {
constructor(executor) { ... }

/**
* then 方法
* @returns {Promise<object>}
* @param onFulfilled<function>: 状态为fulfilled时调用
* @param onRejected<function>: 状态为rejected时调用
*/
then(onFulfilled, onRejected) {
// 返回一个新的Promise实例
const newPromise = new Promise((resolve, reject) => {

if (this.state === 'fulfilled') {
const x = onFulfilled(this.value)

// 对返回值进行处理
resolvePromise(newPromise, x, resolve, reject);
}

if (this.state === 'rejected') {
const x = onRejected(this.reason);

// 对返回值进行处理
resolvePromise(x, resolve, reject);
}

if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
const x = onFulfilled(this.value);

// 对返回值进行处理
resolvePromise(newPromise, x, resolve, reject);
})
this.onRejectedCallbacks.push(() => {
const x = onRejected(this.reason);

// 对返回值进行处理
resolvePromise(newPromise, x, resolve, reject);
})
}
});

return newPromise;
}
}

function resolvePromise() {}

完成resolvePromise函数

对于上一个then()的返回值,我们用x变量存起来,然后需要对它进行一个处理:

  • 判断x是不是Promise实例;
    • 如果是Promise实例,则取它的结果,作为新的Promise实例成功的结果;
    • 如果是普通值,直接作为Promise成功的结果;

然后我们处理返回值后,需要利用newPromiseresolvereject方法将结果返回。

这里我们还需要注意一个地方,就是x等于newPromise的话,这时会造成循环引用,导致死循环。

1
2
3
4
5
6
7
let p = new Promise(resolve => {
resolve(0);
});
const p2 = p.then(data => {
// 循环引用,自己等待自己完成,导致死循环
return p2;
})

因此,resolvePromise函数需要4个参数,即newPromisexresolvereject

所以我们来实现一下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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* resolvePromise 方法
* @param newPromise<object>: 新的Promise实例
* @param x<any>: 上一个then()的返回值
* @param resolve<function>:Promise实例的resolve方法
* @param reject<function>:Promise实例的reject方法
*/
function resolvePromise(newPromise, x, resolve, reject) {
// 循环引用报错
if(x === newPromise){
// reject报错
return reject(new TypeError('Chaining cycle detected for promise'));
}
// 防止多次调用
let called;
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
// x 为Promise实例
if (typeof then === 'function') {
// 使用call执行then(),call的第一个参数是this,后续即then()的参数,即第二个是成功的回调方法,第三个为失败的回调函数
then.call(x, y => {
// 成功和失败只能调用一个
if(called)return;
called = true;
// resolve 的结果依旧是promise实例,那就继续解析
resolvePromise(newPromise, y, resolve, reject);
}, err => {
// 成功和失败只能调用一个
if(called)return;
called = true;
// 失败了就直接返回reject报错
reject(err);
})
} else {
// x 为普通的对象或方法,直接返回
resolve(x);
}
} catch (e) {
if(called)return;
called = true;
reject(e);
}
} else {
// x 为普通的值,直接返回
resolve(x);
}
}

onFulfilledonRejected

关于then()的两个参数——onFulfilledonRejected

  • 它们都是可选参数,而且它们都是函数,如果不是函数的话,就会被忽略掉;
    • 如果onFulfilled不是一个函数,就将它直接替换成函数value => value
    • 如果onRejected不是一个函数,就将它直接替换成函数err => {throw err};
1
2
3
4
5
6
7
8
9
10
11
12
class Promise {
constructor(executor) { ... }

then(onFulfilled, onRejected) {
// onFulfilled如果不是函数,就忽略onFulfilled,直接返回value
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
// onRejected如果不是函数,就忽略onRejected,直接抛出错误
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };

...
}
}

其次,onFulfilledonRejected是不能同步被调用的,必须异步调用。因此我们就用setTimeout解决一步问题。

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
class Promise {
constructor(executor) { ... }

then(onFulfilled, onRejected) {
// onFulfilled如果不是函数,就忽略onFulfilled,直接返回value
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
// onRejected如果不是函数,就忽略onRejected,直接抛出错误
onRejected = typeof onRejected === 'function' ? onRejected : err => {
throw err
};

return new Promise((resolve, reject) => {
if (this.state === 'fulfilled') {
// 异步调用
setTimeout(() => {
try {
const x = onFulfilled(this.value)
resolvePromise(x, resolve, reject);
}catch (e){
reject(e)
}
})
}

if (this.state === 'rejected') {
// 异步调用
setTimeout(() => {
try{
const x = onRejected(this.reason);

resolvePromise(x, resolve, reject);
}catch (e){
reject(e)
}
})
}

if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
// 异步调用
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(x, resolve, reject);
}catch (e){
reject(e)
}
})
})
this.onRejectedCallbacks.push(() => {
// 异步调用
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(x, resolve, reject);
}catch (e){
reject(e)
}
})
})
}
});
}
}

实现Promise的其他方法

Promise.all()

Promise.all()方法接收一个promiseiterable类型的输入,包括ArrayMapSet。然后返回一个Promise实例,该实例回调返回的结果是一个数组,包含输入所有promise的回调结果。

但只要任何一个输入的promisereject回调执行或者输入不合法的promise,就会立马抛出错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Promise.all 方法
* @returns {Promise<object>}
* @param promises<iterable>: 一个promise的iterable类型输入
*/
Promise.all = function (promises) {
let arr = [];

return new Promise((resolve, reject) => {
if (!promises.length) resolve([]);
// 遍历promises
for(const promise of promises) {
promise.then(res => {
arr.push(res);
if(arr.length === promises.length){
resolve(arr);
}
}, reject)
}
})
}

Promise.allSettled()

Promise.allSettled()其实跟Promise.all()很像,同样是接收一个promiseiterable类型的输入,但返回的是一个给定的promise已经完成后的promise,并带有一个对象数组,每个对象标识着对应的promise结果。

1
2
3
4
5
6
7
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];

Promise.allSettled(promises).
then((results) => console.log(results));
// > Array [Object { status: "fulfilled", value: 3 }, Object { status: "rejected", reason: "foo" }]

实现:

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
/**
* Promise.allSettled 方法
* @returns {Promise<object>}
* @param promises<iterable>: 一个promise的iterable类型输入
*/
Promise.allSettled = function (promises) {
let arr = [];

return new Promise((resolve, reject) => {
try {
const processData = (data) => {
arr.push(data);
if(arr.length === promises.length){
resolve(arr);
}
}

if (!promises.length) resolve([]);
// 遍历promises
for(const promise of promises) {
promise.then(res => {
processData({state:'fulfilled', value: res})
}, err => {
processData({state:'rejected', reason: err})
})
}
}catch (e){
reject(e)
}
})
}

Promise.any()

Promise.any()Promise.all()Promise.allSettled()一样,同样是接收一个promiseiterable类型的输入。但只要其中的一个promise成功,就返回那个已经成功的promise,但如果没有一个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
/**
* Promise.any 方法
* @returns {Promise<object>}
* @param promises<iterable>: 一个promise的iterable类型输入
*/
Promise.any = function (promises) {
return new Promise((resolve, reject) => {
// 如果传入的参数是一个空的可迭代对象,则返回一个 已失败(already rejected) 状态的 Promise
if (!promises.length) reject();
// 如果传入的参数不包含任何 promise,则返回一个 异步完成 (asynchronously resolved)的 Promise。
if (typeof promises[Symbol.iterator] !== 'function' ||
promises === null ||
typeof promises === 'string') {
resolve()
}

let i = 0;
// 遍历promises
for (const promise of promises) {
promise.then(res => {
i++;
resolve(res);
}, err => {
i++;
if (i === promises.length) {
reject(err);
}
})
}
})
}

Promise.race()

Promise.race(),同样是接收一个promiseiterable类型的输入。一旦迭代器中的某个promise完成了,不管是成功还是失败,就会返回这个promise

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Promise.race 方法
* @returns {Promise<object>}
* @param promises<iterable>: 一个promise的iterable类型输入
*/
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
for (const promise of promises) {
promise.then(resolve, reject)
}
})
}

Promise.reject()Promise.resolve()

Promise.reject()方法返回一个带有拒绝原因的Promise对象;Promise.resolve()方法返回一个以定值解析后的Promise对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Promise.reject 方法
* @returns {Promise<object>}
* @param val<any>
*/
Promise.reject = function (val) {
return new Promise(reject => reject(val))
}

/**
* Promise.resolve 方法
* @returns {Promise<object>}
* @param val<any>
*/
Promise.resolve = function (val) {
return new Promise(resolve => resolve(val))
}

catch()finally()

catch()方法是用来处理失败的情况,它传入一个处理函数,然后返回一个promise实例。实际上它是then()的语法糖,只接受rejected态的数据。

finally()是在promise结束时,无论结果是fufilled还是rejected,都会执行指定的回调函数。同样也返回一个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
class Promise {
constructor(executor) { ... }

then(onFulfilled, onRejected) { ... }

/**
* catch 方法
* @returns {Promise<object>}
* @param callback<function>: 处理函数
*/
catch(callback) {
return this.then(null, callback);
}

/**
* finally 方法
* @returns {Promise<object>}
* @param callback<function>: 处理函数
*/
finally(callback) {
return this.then(res => {
return Promise.resolve(callback()).then(() => res)
}, err => {
return Promise.reject(callback()).then(() => {
throw err
})
})
}
}

Promise/A+测试

Promise/A+规范: https://github.com/promises-aplus/promises-spec

Promise/A+测试工具: https://github.com/promises-aplus/promises-tests

安装promises-aplus-tests插件。

1
yarn add promises-aplus-tests

Promise.js后面插入下列代码。

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

然后输入命令行进行测试。

1
promises-aplus-tests Promise.js

结果:

1
872 passing (18s)
 Comments