跳到主要内容

手写Promise

· 阅读需 25 分钟
熊滔

Promise 的使用

回调地狱

首先我们了解一下 Promise 出现的背景,假设有下面的程序

let name = getUserNameById(id);
let score = getScoreByName(name);
let scholarship = getScholarshipByScore(score);
console.log(scholarship);

这个程序首先根据 id 去获取名字,接着根据拿到的名字取获得分数,最后根据分数去获取奖学金,最后打印出奖学金。但是这个程序真的能达到预期的效果吗? 答案是不能,因为 JavaScript 是异步的,对于一般的耗时操作并不会立即执行,而是将函数保存在一个队列中,直到代码执行完毕,才会拿出队列中的函数执行。所以上面的函数都不会被立即执行,所以当然没有返回值,所以上面的 name, score, scholarship 都是 undefined

为了解决这种情况,我们一般会使用回调函数的形式,等我们根据 id 拿到 name 之后,将 name 传入回调函数,这样就可以保证"同步"的效果,所以我们将上面的代码修改如下

getUserNameById(id, function (name) {
getScoreByName(name, function (score) {
getScholarshipByScore(score, function (scholarship) {
console.log(scholarship);
});
});
});

但是上面的程序看起来还是怪吓人的,函数套着函数,像这样的情况我们称之为回调地狱。回调地狱不能使用 try... catch 捕获异常,不能使用 return,并且代码的可读性差,还容易出错。可能现在你还体会不到,上面我们只是嵌套了三层,实际的情况可能有更多的层,使得代码很难阅读。

Promise 的简单使用

正是为了解决这样的问题,在 ES6 中提出了 Promise 来解决这个问题,我们来看如何使用 Promise,看下面的程序

let p = new Promise((resolve) => {
// fn1
setTimeout(() => {
console.log("1s");
resolve(1);
}, 1000);
});

p.then((value) => {
// fn2
console.log(value);
});

Promise 构造函数接受一个回调函数 fn1,并返回一个对象,该对象有一个 then 方法,then 方法也接受一个回调函数 fn2,只有当 fn1 中接收 resolve 函数执行(当 resolve 执行以后,我们就说该 Promise 对象进入了 fulfilled 状态),fn2 才会被执行。

以上面的代码为例,首先我们往 Promise 中传递了一个回调函数 fn1,该函数会立即执行

// 向构造函数中传入的回调函数 fn1
(resolve) => {
setTimeout(() => {
console.log("1s");
resolve(1);
}, 1000);
};

当该 fn1resolve 函数执行之后,fn2 才会被执行,所以 1s 之后 fn2 才会执行

// then 接收的回调函数 fn2
(value) => {
console.log(value);
};

fn2 接收的参数 value 是通过 resolve 传入的,在上面我们通过 resolve(1) 传入了参数 1,所以 fn2 接受到的参数为 1。上面执行的结果为

可见 fn2 是在 resolve 函数之后执行的,这样就可以保证代码执行的顺序,并且不用写嵌套的回调函数,而是使用 then 方法进行"平行"的调用。

链式调用

所谓的链式调用是指在调用 then 方法之后还可以继续调用 then 方法,如下

let p = new Promise((resolve) => {
// fn1
resolve(1);
});

p.then((value) => {
// fn2
console.log(value);
return 2;
}).then((value) => {
// fn3
console.log(value);
});

输出如下

1;
2;

如上所示,我们连续调用了两次 then,在执行时,fn3 是在 fn2 之后执行的,并且 fn3 中的参数 valuefn2 的返回值。其实 then 方法也可以不传递任何的参数

let p = new Promise((resolve) => {
// fn1
resolve(1);
});

p.then((value) => {
// fn2
console.log(value);
return 2;
}).then() // 没有传递任何的参数
.then((value) => {
// fn3
console.log(value);
});

在上面我们调用了没有传入回调函数 then 之后又继续调用了一个 then,根据我们上面所讲的,fn3 接受的参数 value 是第二个 then 回调函数的返回值,但是由于第二个 then 没有回调函数,意味着根本没有返回值,这个时候会把上一层的返回值即 fn2 返回值传到 fn3

所以上面的输出是

1
2

then 中的回调函数除了可以返回普通的值以外,还可以返回一个 Promise 对象,当返回 Promise 对象时,下面如果继续调用 then,那么 then 中的回调函数要等到这个 Promise 对象中的 resolve 方法执行后才会被执行,即进入了 fulfilled 状态 ,并且这个 then 中回调函数所需的参数是返回的 Promise 对象 resolve 传入的值,如下

let p = new Promise((resolve) => {
resolve(1);
});

p.then((value) => {
// fn1
console.log(value);
return new Promise((resolve) => {
setTimeout(() => {
console.log("then 返回的Promise");
resolve(2);
}, 1000);
});
}).then((value) => {
// fn2
console.log(value);
});

fn2fn1 返回的 Promise 进入了 fulfilled 状态之后才会被执行

上面的执行结果为

原型方法

上面我们介绍了 resolvethen 方法,当我们执行 resolve 方法之后,then 接收的回调函数才会执行。其实传入 Promise 的回调函数有两个参数,一个是 resolve,另一个是 reject,当函数产生错误时,那么我们会调用 reject 方法,这时我们说 Promise 对象进入了 rejetced 状态,这时后面的 then 方法不会执行,与 then 方法相对的是一个 catch 方法,该方法也是接收一个回调函数,该回调函数时用来处理错误的,当该 Promise 对象进入了 rejected 状态,catch 中的回调函数才会被执行,与 then 相似,catch 中回调函数接收的参数是 reject 调用时传入的参数,如下

let p = new Promise((resolve, reject) => {
reject("error");
}).then(() => {
console.log("then方法执行了");
})
.catch((error) => {
console.log("catch方法执行");
console.log("message: " + error);
});

结果为

catch方法执行;
message: error;

上面我们调用了 reject,这时后面的 then 中的回调函数是不会执行的,而是会执行 catch 中的回调函数。

note

其实 then 方法可以接受两个回调函数,第一个回调函数用来处理 resolve 之后的结果,而第二个回调函数则是用来处理 reject 之后的结果,比如上面的程序可以写为

let p = new Promise((resolve, reject) => {
reject("error");
}).then(
() => {
console.log("then方法执行了");
},
(error) => {
console.log("catch方法执行");
console.log("message:" + error);
}
);

执行的结果与上面的相同,其实 catch(onError) 函数本质上就是 then(null, onError)

有的时候无论是成功以否,我们都希望执行一个函数,该函数的作用一般是用来资源的回收,用来完成这个功能的是 finally 函数,该函数也是接收一个回调函数,该回调函数无论是在 fulfilled 状态还是 rejected 状态都是会被执行的

let p = new Promise((resolve, reject) => {
// rejected状态
reject();
})
.then(
() => {
console.log("then方法执行了");
},
() => {
console.log("catch方法执行");
}
)
.finally(() => {
console.log("finally执行了");
});

最后的执行结果为

catch方法执行;
finally执行了;

现在我们将 reject() 改为 resolve()

let p = new Promise((resolve, reject) => {
// fulfilled 状态
resolve();
}).then(
() => {
console.log("then方法执行了");
},
() => {
console.log("catch方法执行");
}
)
.finally(() => {
console.log("finally执行了");
});

这时执行的结果为

then方法执行了
finally执行了

所以不论出于哪个状态,finally 中的回调函数都会被执行。

静态方法

静态方法指的就是通过 Promise 直接调用的方法。

resolve

我们首先来介绍 resolve 方法,该函数接收一个参数,返回一个 Promise 对象,根据参数的不同情况,返回值也不相同

  1. 参数是一个 Promise 对象,直接返回

    let p1 = new Promise((resolve, reject) => {
    resolve(123);
    });

    let p2 = Promise.resolve(p1);

    console.log(p1 === p2); // true
  2. 参数是一个含有 then 方法的对象(我们也称之为 thenable 对象),那么会立即执行该 then 方法,该 then 方法接收两个参数,这两个参数分别为返回的 Promiseresolve, reject 方法

    let p = Promise.resolve({
    then(resolve, reject) {
    resolve(123);
    },
    });

    p.then((value) => {
    console.log(value); // 123
    });
  3. 参数不是上面两种情况,那么会返回的一个 fulfilled 状态的 Promise,该 Promise 会将参数传给后面的 then

    let p = Promise.resolve("123");

    p.then((value) => {
    console.log(value); // 123
    });

all

该方法也会返回一个 Promise 对象,它接受一个由 Promise 对象组成的数组,只有当该数组中的所有 Promise 对象都变为 fulfilled 状态之后,返回的 Promise 对象才会变为 fulfilled 状态

let p1 = new Promise((resolve) => {
setTimeout(() => {
console.log("p1");
resolve(1);
}, 1000);
});

let p2 = new Promise((resolve) => {
setTimeout(() => {
console.log("p2");
resolve(2);
}, 2000);
});

let p3 = new Promise((resolve) => {
setTimeout(() => {
console.log("p3");
resolve(3);
}, 3000);
});

Promise.all([p1, p2, p3]).then((results) => {
console.log(results); // [ 1, 2, 3 ]
});

在上面我们定义三个 Promise 对象,这个三个对象分别在 1s, 2s, 3s 后变为 fulfilled,所以返回的 Promise 对象在 3s 后变为 fulfilled 状态,并且会将这三个 Promise 对象向 resolve 传入的值形成数组传入到返回的 Promiseresolve

上面代码的执行结果为

race

race 方法它也会返回一个 Promise 对象,同 all 方法一样,它也接受一个由 Promise 对象组成的数组,但是不是当所有的 Promise 对象变为 fulfilled 后,返回的 Promise 对象才会变为 fulfilled,而是这些 Promise 对象进行竞赛,当最快的一个 Promise 对象变为 fulfilled 状态时,返回的 Promise 对象就会变为 fulfilled

let p1 = new Promise((resolve) => {
setTimeout(() => {
console.log("p1");
resolve(1);
}, 1000);
});

let p2 = new Promise((resolve) => {
setTimeout(() => {
console.log("p2");
resolve(2);
}, 2000);
});

let p3 = new Promise((resolve) => {
setTimeout(() => {
console.log("p3");
resolve(3);
}, 3000);
});

Promise.race([p1, p2, p3]).then((result) => {
console.log(result);
});

并且会将向最快变为 fulfilled 状态的 Promise 对象的 resolve 传入的值传入到返回的 Promise 对象的 resolve 方法中

所以上面程序的执行结果为

可见当 p1 变为 fulfilled 之后,返回的 Promise 也变为了 fulfilled 状态,并且拿到了向 p1resolve 中传入的值。

实现 Promise

简单实现

下面就将简单实现一个 Promise。首先我们要明确,Promise 有三个状态,分别为 pending, fulfilled, rejected 三个状态,初始时的 Promisepending 状态,当调用 resolve 方法后就会变为 fulfilled 状态,当处于 fulfilled 状态时,then 中的第一个回调函数才会被执行;调用 reject 方法就会变为 rejected 状态,当处于 rejected 状态时,then 中的第二个回调函数才会被执行或者 catch 中的回调函数才会被执行。并且 fulfilledrejected 状态只能由 pending 状态转变而来,并且一般转变为 fulfilled 状态或者 rejected 状态,那么状态就不能再次进行转变

class Promise {
// Promise的三个状态
PENDING = "pending";
FULFILLED = "fulfilled";
REJECTED = "rejected";

// 当前状态
state = this.PENDING;
// 传给 then 中回调函数的值
value = null;

// 构造函数,接收一个回调函数,立即执行该函数并将 _resolve 和 _reject 传入
constructor(fn) {
fn(this._resolve.bind(this), this._reject.bind(this));
}

// 当执行到 then 方法但还未变为 fulfilled 状态时,那么将向 then 中传入的回调函数先保存起来
callbacks = [];

_resolve(value) {
// 如果状态不为 pending,说明状态已发生改变,不再执行
if (this.state !== this.PENDING) {
return;
}

// 将要传给 then 中回调函数的值保存起来
this.value = value;
// 改变状态
this.state = this.FULFILLED;
// 执行向 then 中传入的 onFulfilled 函数
this.callbacks.forEach((callback) => callback.onFulfilled(this.value));
}

_reject(error) {
// 过程同 reject
if (!this.PENDING) {
return;
}
this.value = error;
this.state = this.REJECTED;
this.callbacks.forEach((callback) => callback.onRejected(this.value));
}

then(onFulfilled, onRejected) {
// 如果当前的状态为 pending,那么将回调函数保存到 callbacks 函数中,等到状态改变时执行
if (this.state === this.PENDING) {
this.callbacks.push({
onFulfilled,
onRejected,
});
return;
}

// 如果状态为 fulfilled 状态,则执行 onFulfilled 方法
if (this.state === this.FULFILLED) {
onFulfilled(this.value);
return;
}

// 同上
if (this.state === this.REJECTED) {
onRejected(this.value);
return;
}
}
}

上面的程序的注释描述了程序的功能,相比还是不难理解的,现在我们来测试一下是否有效

let p = new Promise((resolve) => {
setTimeout(() => {
console.log("1s后");
resolve(1);
}, 1000);
});

p.then((value) => {
console.log(value);
});

结果如下

链式调用

现在我们来实现链式调用,要实现链式调用就需要返回一个 Promise,每次调用 then 方法我们都返回一个新的 Promise,修改如下

_resolve(value) {
if (this.state !== this.PENDING) {
return;
}
this.value = value;
this.state = this.FULFILLED;
// 修改了这里,由于需要将 then 中的回调函数的返回值返回,所以不能简单的调用
this.callbacks.forEach(callback => this._handle(callback));
}

_reject(error) {
if (!this.PENDING) {
return;
}
this.value = error;
this.state = this.REJECTED;
// 同 resolve
this.callbacks.forEach(callback => this._handle(callback));
}

then(onFulfilled, onRejected) {
// 返回新的 Promise
return new Promise((resolve, reject) => {
this._handle({
onFulfilled,
onRejected,
resolve,
reject
})
})
}

_handle(callback) {
// 如果是 pending 状态,将 callback 延迟执行
if (this.state === this.PENDING) {
this.callbacks.push(callback);
}
if (this.state === this.FULFILLED) {
// 如果 then 方法没有传入 onFulFilled 回调函数,那么将上一层返回的值传入
if (!callback.onFulfilled) {
callback.resolve(this.value);
return;
}
// 如果有 onFulFilled 函数,那么将 onFulFilled 函数的返回值传入到返回的 Promise 对象的 resolve 中
try {
// 用户传入的回调函数可能会出错,所以使用 try...catch 包裹起来
let ret = callback.onFulfilled(this.value);
callback.resolve(ret);
} catch (e) {
// 当传入的回调函数出错时,Promise 变为 rejected状态
callback.reject(e);
}
}
if (this.state === this.REJECTED) {
if (!callback.onRejected) {
callback.reject(this.value);
return;
}
let ret = callback.onRejected(this.value);
callback.reject(ret);
}
}

现在我们来测试一下是否能够进行链式调用

let p = new Promise((resolve) => {
setTimeout(() => {
console.log("1s后");
resolve(1);
}, 1000);
});

p.then((value) => {
console.log(value);
return 2;
}).then() // 什么回调函数都没有传入,会将上一个 then 返回的值传入
.then((value) => {
console.log(value);
});

上面的执行结果为

上面我们还要最后一个问题没有解决,就是如果 then 中的回调函数返回的是 Promise 对象,那么我们就要在 _resolve 对值进行判断,如果值是 Promise 对象,记作 p1,那么 then 返回的 Promise 对象 p2resolve 方法应当在 p1 对象的 resolve 执行之后执行。修改 _resolve 如下

_resolve(value) {
if (this.state !== this.PENDING) {
return;
}

// 只增加了下面的代码
if (value instanceof Promise) {
// 当前 resolve 的执行应当在 value 的 resolve 执行之后
value.then(this._resolve.bind(this), this._reject.bind(this));
return;
}

this.value = value;
this.state = this.FULFILLED;
this.callbacks.forEach(callback => this._handle(callback));
}

现在我们来验证一番

let p = new Promise((resolve) => {
setTimeout(() => {
console.log("1s后");
resolve(1);
}, 1000);
});

p.then((value) => {
console.log(value);
return new Promise((resolve) => {
setTimeout(() => {
console.log("又1s后");
resolve(2);
}, 1000);
});
}).then((value) => {
console.log(value);
});

执行结果为

原型方法

接下来继续实现 Promise 对象的几个原型方法,分别是 catchfinallycatch 的实现很简单

catch(onRejected) {
return this.then(null, onRejected);
}

接下来是实现 finally 方法,最容易想到的版本是

finally(onDone) {
return this.then(onDone, onDone);
}

不管是处于 fulfilled 的状态还是 rejected 的状态,onDone 方法都会得到执行,但是使用这样的方式有缺点

  1. onDone 方法是无论失败还是成功时都会执行的,所以它应该没有参数,但是使用 then(onDone, onDone) 的方式就会传入参数
  2. 如果 onDone 返回一个 Promise 对象的话,那么会改变 finally 返回的 Promise 的状态

处于上述考虑,我们使用下面的实现方式

finally(onDone) {
if (typeof onDone !== 'function') {
return this.then();
}
// 无论成功与否,onDone() 都会执行,且不需要参数
// 另外执行 finally 不会影响之前的 Promise 状态
return this.then(
value => Promise.resolve(onDone()).then(() => value),
error => Promise.resolve(onDone()).then(() => {throw error})
);
}

静态方法

最后来实现 Promise 的静态方法,首先实现 resolve 方法,对于该方法的使用我们在用法那里已经介绍过了,所以这里直接贴出实现的代码

static resolve(value) {
// 如果传入的是 Promise 对象,直接返回
if (value instanceof Promise) {
return value;
}

// 如果传入的是 thenable 对象,则立即执行对象的 then,并将 resolve 和 reject 传入
if (value && typeof value === 'object' && typeof value.then === 'function') {
return new Promise((resolve, reject) => value.then(resolve, reject));
}

if (value) {
// 如果不是上面两种情况,并且 value 存在,那么直接将 value 传入 resolve
return new Promise(resolve => resolve(value));
} else {
return new Promise(resolve => resolve());
}
}

all 方法在上面也介绍过了,只有当所有传入的 Promise 对象都变为 fulfilled 状态,返回的 Promise 对象才会变为 fulfilled 状态,所以我们使用一个变量来统计已经变为 fulfilled 状态的 Promise 对象的个数,当所有 Promise 对象都变为 fulfilled 状态时,执行 resolve 方法,将返回的 Promise 对象变为 fulfilled 状态,如下

static all(promises) {
return new Promise((resolve, reject) => {
// Promise 对象的个数
let itemLength = promises.length;
// 统计已变为 fulfilled 状态的 Promise 对象个数
let finishedPromise = 0;
// 返回的数组
let results = Array.from({length: itemLength});

promises.forEach(promise => {
promise.then(result => {
results[finishedPromise] = result;
finishedPromise++;
// 当所有的 Promise 对象变为 fulfilled 时,返回的 Promise 对象状态变为 fulfilled
if (finishedPromise == itemLength) {
resolve(results);
}
}, error => {
// 只要有一个变为 rejected,那么直接变为 rejected 状态
reject(error);
})
})
})
}

race 方法的用法也介绍过,在这里我们利用 resolve 方法只会执行一次的特性,我们很快可以写出这样的代码

static race(promises) {
return new Promise((resolve, reject) => {
promises.forEach(promise => {
promise.then(result => {
resolve(result);
}, error => {
reject(error);
})
})
})
}

由于 resolve 只会执行一次,只有最先变为 fulfilled 状态的 Promise 对象能将它 resolve 的值传入。

完整代码

class Promise {
PENDING = "pending";
FULFILLED = "fulfilled";
REJECTED = "rejected";

state = this.PENDING;
value = null;

constructor(fn) {
fn(this._resolve.bind(this), this._reject.bind(this));
}

callbacks = [];

_resolve(value) {
if (this.state !== this.PENDING) {
return;
}

if (value instanceof Promise) {
value.then(this._resolve.bind(this), this._reject.bind(this));
return;
}

this.value = value;
this.state = this.FULFILLED;
this.callbacks.forEach((callback) => this._handle(callback));
}

_reject(error) {
if (!this.PENDING) {
return;
}
this.value = error;
this.state = this.REJECTED;
this.callbacks.forEach((callback) => this._handle(callback));
}

then(onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
this._handle({
onFulfilled,
onRejected,
resolve,
reject,
});
});
}

catch(onRejected) {
return this.then(null, onRejected);
}

finally(onDone) {
if (typeof onDone !== "function") {
return this.then();
}

return this.then(
(value) => Promise.resolve(onDone()).then(() => value),
(error) =>
Promise.resolve(onDone()).then(() => {
throw error;
})
);
}

_handle(callback) {
if (this.state === this.PENDING) {
this.callbacks.push(callback);
}

if (this.state === this.FULFILLED) {
if (!callback.onFulfilled) {
callback.resolve(this.value);
return;
}

try {
let ret = callback.onFulfilled(this.value);
callback.resolve(ret);
} catch (e) {
callback.reject(e);
}
}

if (this.state === this.REJECTED) {
if (!callback.onRejected) {
callback.reject(this.value);
return;
}
let ret = callback.onRejected(this.value);
callback.reject(ret);
}
}

static resolve(value) {
if (value instanceof Promise) {
return value;
}

if (
value &&
typeof value === "object" &&
typeof value.then === "function"
) {
return new Promise((resolve, reject) => value.then(resolve, reject));
}

if (value) {
return new Promise((resolve) => resolve(value));
} else {
return new Promise((resolve) => resolve());
}
}

static all(promises) {
return new Promise((resolve, reject) => {
let itemLength = promises.length;
let finishedPromise = 0;
let results = Array.from({ length: itemLength });

promises.forEach((promise) => {
promise.then(
(result) => {
results[finishedPromise] = result;
finishedPromise++;
if (finishedPromise == itemLength) {
resolve(results);
}
},
(error) => {
reject(error);
}
);
});
});
}

static race(promises) {
return new Promise((resolve, reject) => {
promises.forEach((promise) => {
promise.then(
(result) => {
resolve(result);
},
(error) => {
reject(error);
}
);
});
});
}
}

参考文章