手写Promise

说到异步编程,大家应该会想到Promise。想必对它对典型的认知应该就是——解决了回调地狱的问题,通过链式调用(不停的.then .then)的方式来避免大量嵌套。对于我个人而言,在使用Promise的过程中还是难免出现一些问题,感觉仍停留在稀里糊涂用的这么一个阶段。今天我想通过实现一个Promise来彻底搞懂其原理。
Promise我们可以称之为一个容器,它里边保存了一个异步操作的最终结果。这里有一个Promises/A+规范,我们可以按照其中的描述来一步一步的实现一个符合要求的Promise。
Promise有三个状态,等待、成功和失败,默认是等待状态。并且一旦成功或者失败了状态就不能改变了。
resolve接收一个函数作为参数,我们称之为excutor,它是一个执行器,会立即执行。它有两个参数resolve和reject,分别代表成功和失败。resolve代表的是成功,它会接收一个参数value,状态改变为fulfilled。reject代表的是失败,接收参数reason,状态改变为rejected。这里要注意的是,状态变为成功或者失败后不能再次被更改,如果new Promise的时候throw new Error报错也会变成失败态。
每个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
const PENDING = 'PENDING';
const RESOLVED = 'RESOLVED';
const REJECTED = 'REJECTED';

class Promise{
constructor(executor){
this.status = PENDING;//默认是PENDING状态
this.value = undefined;//成功
this.error = undefined;//失败
let resolve = value => {
if (this.status === PENDING)//状态为PENDING时才能改变 {
this.status = RESOLVED;
this.value = value;
}
};
let reject = error => {
if (this.status === PENDING) {
this.status = REJECTED;
// 储存失败信息
this.error = error;
}
};
try{
executor(resolve, reject);
} catch (e) {
reject(e);//执行器执行时内部可能报错
}
}
}

每个promise实例都有一个then方法,可以传入两个参数,onFulfilled和onRejected。如果当前状态成功我们调用onfFlfilled,失败调用onRejected.

1
2
3
4
5
6
7
8
9
10
then(onFulfilled,onRejected) {
// 状态为fulfilled,执行onFulfilled,传入成功的值
if (this.status === RESOLVED) {
onFulfilled(this.value);
};
// 状态为rejected,执行onRejected,传入失败的原因
if (this.state === REJECTED) {
onRejected(this.error);
};
}

当resolve在setTomeout里运行的时候,上边的写法就不能满足要求啦。我们可以采取发布订阅的思想,现将要执行的方法存到数组里,当状态改变后再执行对应的方法。

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
let resolve = value => {
if (this.state === PENDING) {
this.state = RESOLVED;
this.value = value;
this.onResolvedCallbacks.forEach(fn=>fn());
}
};
let reject = error => {
if (this.status === PENDING) {
this.status = REJECTED;
this.error = reason;
this.onRejectedCallbacks.forEach(fn=>fn());
}
};

then(onFulfilled, onRejected) {
if (this.status === PENDING) {
this.onResolvedCallbacks.push(()=>{
onFulfilled(this.value);
})
this.onRejectedCallbacks.push(()=>{
onRejected(this.error);
})
}
}

我们需要判断then中传递函数的返回结果。当返回结果是promise时,则采用其状态。如果不是promise。将结果传递下去即可。Promise通过链式调用来解决回调地狱,也就是在第一个then里返回里一个Promise。我们可以在then里面返回一个新的promise,称为promise2。

1
2
3
4
5
6
let promise2 = new Promise((resolve, reject)=>{
if (this.status === RESOLVED) {
let x = onFulfilled(this.value);
// 当x为普通值时,通过resolve保存值。如果是promise则要调用then。我们可以通过一个公共方法来解析x的值和promise2的关系。这里要注意promise必须声明完后才能传入resolvePromise方法,我们可以用setTimeout宏任务做延迟,可以保证得到的是声明后的promise2
resolvePromise(promise2, x, resolve, reject);
};

接下来我们来实现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
function resolvePromise(promise2, x, resolve, reject){
//首先它的核心功能是判断x的值是不是resolve。而且它的写法要兼容所有的promise情况
// 如果promise2和x链接的是同一个对象,则报错。比如let promise2 = p.then(() => { return promise2 })
if(x === promise2){
// reject报错
return reject(new TypeError('循环引用啦'));
}
// 防止多次调用
let called;
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
// A+规定,声明then = x的then方法
let then = x.then;
// 如果then是函数, 可以默认认为是promise了
if (typeof then === 'function') {
//y可能还是一个promise,直到解析出的结果是一个普通值
then.call(x, y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, err => {
if (called) return;
called = true;
reject(err);// 采用失败结果向下传递
})
} else {
resolve(x); // 说明x是普通对象,直接成功即可
}
} catch (e) {
//防止多次调用成功和失败
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}

onFulfilled和onRejected均为可选参数。
onFulfilled返回一个普通的值,成功时直接等于 data => data
onRejected返回一个普通的值,失败时如果直接等于 data => data,则会跑到下一个then中的onFulfilled中,所以直接扔出一个错误

1
2
3
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : data => data;
// onRejected如果不是函数,就忽略onRejected,直接扔出错误
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };

实现了Promise,那么它的all方法和race方法相对来说就比较好实现了。
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
只有传入所有实例的状态都变成fulfilled,新的promises的状态才会变成fulfilled,此时每个实例的返回值组成一个数组,传递给promises的回调函数。
只要之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给整个promises的回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Promise.all = function (promises) {
//promises是一个promise的数组
return new Promise(function (resolve, reject) {
let arr = []; //arr是最终返回值的结果
let successCount = 0; // 表示成功了多少次
function processData(index, data) {
arr[index] = data;
successCount++;
if (successCount === promises.length) {
resolve(arr);
}
}
for (let i = 0; i < promises.length; i++) {
promises[i].then(function (data) {
processData(i, data)
}, reject)
}
})
}

Promise.race也是将多个Promise实例包装成一个新的实例,只要有其中一个实例改变状态那么整个实例状态改变并停止执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Promise.race = function(promises) {
return new Promise((resolve, reject) => {
let len = promises.length;
if(len === 0) return;
for(let i = 0; i < len; i++) {
Promise.resolve(promise[i]).then(data => {
resolve(data);
return;
}).catch(err => {
reject(err);
return;
})
}
})
}