引言
大家好啊,我是前端拿破轮。
如果你参加过前端面试或笔试,一定有一个东西是你绕不过去的,那就是Promise。各种关于Promise的笔面试题目五花八门。今天,拿破轮就带着大家从头到尾完整的实现自己的Promise,所有关于Promise的面试题彻底毕业。
妈妈以后再也不用担心我的Promise了!
概述
要想手写一个Promise,核心只有两个。一个是Promise的构造函数,一个是promise对象的then方法,其他的都是细枝末节。一旦搞定了这两个东西,其余东西就呼之欲出了。
实现Promise构造函数
我们用ES6的类语法来实现构造函数。
1 2 3 4 5 6
| class MyPromise { constructor(executor) { executor(resolve, reject); } }
|
那构造函数如何书写呢,回顾我们的Promise是如何使用的:
1 2 3
| const p = new Promise((resolve, reject) => { resolve(1); })
|
我们可以发现,Promise的构造函数的参数是一个函数,这个函数是我们要同步执行的任务,我们可以给其取名为executor,表示同步任务执行器。这个函数应该自带两个参数,也是两个函数,用来标记同步任务的状态,在同步任务执行过程中,如果调用第一个函数resolve(),则同步任务被标记为fufilled,如果调用第二个函数reject(),则同步任务被标记为失败rejected。这两个函数应该由Promise的构造函数来实现,因为我们在new Promise((resolve, reject))的时候,并没有写resolve和reject的具体内容,而是直接在同步任务中调用他们,就会改变决定Promise的状态。
所以我们需要在Promise的构造函数中定义这两个函数resolve和reject。
1 2 3 4 5 6 7 8
| class MyProimse { constructor(executor) { const resolve = () => {}; const reject = () => {}; excutor(); } }
|
到这里可能有的同学会想,哎你这样直接在构造函数中定义两个函数,那么每个Promise的实例不是都会定义两个函数吗?为什么不直接定义在原型上呢,让这两个函数称为Proimse的实例方法,定义在原型上,所有的Proimse实例都共用这两个方法,这样不是节省内存空间吗?就像下面这样。
1 2 3 4 5 6 7 8 9 10 11 12 13
| class MyProimse { constructor(executor) { executor(this.resolve, this.reject); }
resolve() {
}
reject() {
} }
|
但是这样是不行的,为什么呢?主要就是调用resolve()和reject()时的this指向会出问题。我们调用resolve()和reject()的目的是改变当前Promise实例的状态。所以要让这两个函数的this指向新建的Promise实例。但是我们会想一下我们是怎么调用resolve()和reject()的,如下
1 2 3
| const p = new Promise((resolve, reject) => { resolve(1); })
|
我们是直接调用的。所以他们的this会指向全局对象(严格模式下是undefined),在class中默认时严格模式。所以这并不符合我们的预期。这时候可能有同学又要想了,这简单呀,我直接在传递的时候使用bind()把这两个函数的this绑定为constructor的this不就行了吗。就像下面这样
1 2 3 4 5 6 7 8 9 10 11 12 13
| class MyPromise { constructor(executor) { executor(this.resolve.bind(this), this.reject.bind(this)); }
resolve() {
}
reject() {
} }
|
这就又考察我们的基础知识,bind()函数调用后是会返回一个新的函数的。这不还是相当于创建了两个函数吗?还是每一个实例都会创建两个函数,我们定义在原型上也就失去了意义。
所以,我们不如直接在构造函数中定义这两个函数。
1 2 3 4 5 6 7
| class MyPromise { constructor(executor) { const resolve = () => {}; const reject = () => {}; executor(resolve, reject); } }
|
好,接着我们考虑那resolve()和reject()这两个函数有没有参数呢?再回顾我们的日常使用
1 2 3
| const p = new Promise((resolve, reject) => { resolve(1); })
|
很显然,我们在调用resolve和reject时,可以给他传入参数。resolve(data)表示成功的数据,reject(reason)表示失败的原因。
所以我们在定义他们时,写上参数.
1 2 3 4 5 6 7
| class MyPromise { constructor(executor) { const resolve = (data) => {}; const reject = (reason) => {}; executor(resolve, reject); } }
|
好了,现在我们已经定义好了resolve以及reject的参数,那么他们两个具体要干什么呢?
还是看我们刚才的例子:
1 2 3
| const p = new Promise((resolve) => { resolve(1); });
|
把这段代码放到浏览器中执行,再访问p,如下图:

我们可以看到p是一个promise,有两个属性,一个是[[ProimseState]]是一个字符串”fulfilled”,另一个属性是[[PromiseResult]]表示promise的结果,**就是我们传递给resolve(data)中的data。
所以我们需要在类中定义两个私有变量#state和#result来表示promise的状态和结果。初始值分别为pending和undefined。
当调用resolve(data)时,就将#state设置为fulfilled,把#result的值设置为data。
当调用reject(reason)时,就把#state设置为fulfilled,把#result的值设置为reason。
如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class MyPromise { #state; #result; constructor(executor) { this.#state = 'pending'; this.#result = undefined;
const resolve = (data) => { this.#state = 'fulfilled'; this.#result = data; };
const reject = (reason) => { this.#state = 'rejected'; this.#result = reason; } } }
|
这样看起来挺好的,万事大吉,但是实际上这里面是有bug的。回顾一下,Promise有一个重要特性就是状态一旦确定,就无法再更改。时光不可倒流,一旦调用resolve()或reject()。那么这个promise实例的state和result就确定了下来,以后再也无法修改。
但是我们,目前的实现中很显然是可以修改的。所以我们要对其进行判断限制,如果当前的#state!=='panding',就直接返回,不进行任何设置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class MyPromise { #state; #result; constructor(executor) { this.#state = 'pending'; this.#result = undefined;
const resolve = (data) => { if (this.#state !== 'pending') return; this.#state = 'fulfilled'; this.#result = data; }
const reject = (reason) => { if (this.#state !== 'pending') return; this.#state = 'rejected'; this.#result = reason; } } }
|
然后我们发现resolve和reject中的函数体好像非常的类似,写起来有一种代码重复的感觉,能否给他提取出来呢?不难发现,我们可以提取出如下函数:
1 2 3 4 5 6 7 8
| function chageState(state, result) { if (this.#state !== 'pending') return;
this.#state = state; this.#result = result; }
|
当然这个函数我们只在类的内部使用,所以最好定义为私有的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class MyPromise { #state; #result; constructor(executor) { this.#state = 'pending'; this.#result = undefined; const resolve = (value) => { this.#changeState('fulfilled', value); };
const reject = (reason) => { this.#changeState('rejected', reason); };
executor(resolve, reject); }
#changeState(state, result) { if (this.#state !== 'pending') return; this.#state = state; this.#result = result; } }
|
我们接着思考,我们在代码中硬编码了魔法字符串如pending等,考虑都后续的可维护性,我们可以使用常量来存储字符串。于是我们又进一步优化如下:
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
| const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected";
class MyPromiese { #state; #result; constructor(executor) { this.#state = PENDING; this.#result = undefined; const resolve = (data) => { this.#changeState(FULFILLED, data); }; const reject = (reason) => { this.#changeState(REJECTED, reason); }; executor(resolve, reject); }
#changeState(newState, result) { if (this.#state !== PENDING) return; this.#state = newState; this.#result = result; } }
|
目前代码看起来没有什么问题了,但是实际上还有一个问题。如果在执行executor的过程中报错了怎么办呢?比如下面这样:
1 2 3
| const p = new Promise(() => { throw 123; });
|

我们可以看到,当我们在构造函数的同步执行函数中出错时,Promise的状态会成为rejected,result会成为我们的错误值.
所以我们可以用try..catch语法把executor包起来,当捕获错误时,将#state设置为rejected,将#result设置为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
| const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected";
class MyPromiese { #state; #result; constructor(executor) { this.#state = PENDING; this.#result = undefined; const resolve = (data) => { this.#changeState(FULFILLED, data); }; const reject = (reason) => { this.#changeState(REJECTED, reason); }; try { executor(resolve, reject); } catch (error) { reject(error); } }
#changeState(newState, result) { if (this.#state !== PENDING) return; this.#state = newState; this.#result = result; } }
|
这样我们就实现了符合Promise逻辑的构造函数。但是这里有一个问题,我们使用try...catch的方式,只能捕获同步错误,无法捕获异步错误。
也就是说,如果我们在executor中异步抛出一个错误,比如使用setTimeout,这样的话是捕获不到的。那这怎么办呢,答案就是没有办法。
官方也没有办法,我们可以测试如下代码
1 2 3 4 5
| const p = new Promise(() => { setTimeout(() => { throw 123; }) });
|

我们可以发现,Promise的状态仍然是pending.
实现Promise.prototype.then()
在PromiseA+规范中,实际上并没有规定proimse必须是一个构造函数,或则必须是一个普通函数,或者必须是一个对象等等。什么都没有规定。全篇只规定了只要有一个then方法,then方法符合若干要求,就是一个promise。
首先我们先来回顾,我们在日常开发中如何使用then方法。
1 2 3 4 5 6 7 8 9
| const p = new Promise((resolve, reject) => { resolve(123); });
p.then((res) => { console.log('promise完成', res); }, (err) => { console.log('prommise失败', err); })
|
我们可以看到,then方法应该是promise的一个实例方法,定义在原型上,then方法接收两个函数作为参数。第一个是成功的时候要执行的函数,它有一个参数值res就是我们在Promise的executor中调用resolve(data)时的data。
第二个是失败时候要执行的函数,它也有一个参数值err,就是我们在Promise的executor中调用reject(reason)时的reason。
参数考虑清楚了,我们再来分析,then方法的返回值是什么呢?我们在用的过程中,都知道promise可以链式调用。所以then的返回值应该还是一个promise。
所以,我们已经搞清楚了then方法的函数签名。
1 2 3 4 5
| then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => {
}) }
|
现在有两个问题,第一,我什么时候去调用onFulfilled和onRejected呢?第二,这个then方法返回的promise,什么时候要resolve,什么时候要reject呢?
我们还是回到日常的使用场景:
1 2 3 4 5 6 7 8 9
| const p = new Promise((resolve, reject) => { resolve(123); });
p.then((res) => { console.log('promise完成', res); }, (err) => { console.log('prommise失败', err); })
|
我们可以看到,then方法中的回调,当当前的promise是fulfilled时,会调用then的第一个回调函数;当前的promise是rejected时,会调用then的第二个回调函数。
1 2 3 4 5 6 7 8 9
| then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => { if (this.#state === FUNFILLED) { onFulFilled(this.#result); } else if (this.#state === REJECTED) { onRejected(this.#result); } }) }
|
看起来好像没啥问题了,但是这里面的问题还很大。致命的问题是,我们的代码是同步执行的。这样会出现问题,比如下面的情况:
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
| const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected";
class MyPromise { #state; #result; constructor(executor) { this.#state = PENDING; this.#result = undefined; const resolve = (data) => { this.#changeState(FULFILLED, data); }; const reject = (reason) => { this.#changeState(REJECTED, reason); }; try { executor(resolve, reject); } catch (error) { reject(error); } }
#changeState(newState, result) { if (this.#state !== PENDING) return; this.#state = newState; this.#result = result; }
then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => { if (this.#state === FULFILLED) { onFulfilled(this.#result); } else if (this.#state === REJECTED) { onRejected(this.#result); } }) } }
const p = new MyPromise((resolve, reject) => { setTimeout(() => { resolve("success"); }, 1000); });
p.then((data) => { console.log(data); });
|
上述代码不会有任何输出,因为当我们运行到p.then方法时,此时的p还是pending状态,而根据我们目前写的then函数,pending状态不回执行任何操作。所以我们在then方法中缺失了对于promise时pending的情况的处理。
当promise是挂起时,此时的then应该怎么办呢?这里就很难办了。因为我在then方法中并不知道当前的promise什么时候完成,什么时候拒绝。那then方法不知道,谁知道呢?我们之前写的#changeState方法知道,因为promise无论是要resolve还是reject,都会调用#changeState方法。
但是问题是,我在#changeState方法中倒是想去调用onFulfilled或者onRejected,可是#changeState根本获取不到这两个函数啊。那怎么办呢,聪明的你已经想到了,这还不简单吗,直接把这两个函数保存在实例中不就行了吗?确实,我们要想在#changeState中调用then方法的回调,可以使用这种方式。考虑到我们后续还会调用then方法返回的新的promise的resolve和reject方法,我们可以用一个属性handler存储这四个方法,再写一个私有辅助函数#run来负责调用handler,这样无论是then方法中,还是#changeState方法中,都可以通过调用辅助方法#run来执行handler中的函数。

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
| const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected";
class MyPromise { #state = PENDING; #result = undefined; #handler = null; constructor(executor) { const resolve = (data) => { this.#changeState(FULFILLED, data); }; const reject = (reason) => { this.#changeState(REJECTED, reason); }; try { executor(resolve, reject); } catch (error) { reject(error); } }
#changeState(newState, result) { if (this.#state !== PENDING) return; this.#state = newState; this.#result = result; this.#run(); }
#run () { }
then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => { this.#handler = { onFulfilled, onRejected, resolve, reject, } this.#run(); }) } }
const p = new MyPromise((resolve, reject) => { setTimeout(() => { resolve("success"); }, 1000); });
p.then((data) => { console.log(data); });
|
但是这里有一个问题,一个promise的then方法可能被调用多次,那么当promise已决后,所有的回调都应该执行。所以handlers应该是一个队列,用来记录所有注册的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 49 50 51 52 53 54 55
| const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected";
class MyPromise { #state = PENDING; #result = undefined; #handlers = []; constructor(executor) { const resolve = (data) => { this.#changeState(FULFILLED, data); }; const reject = (reason) => { this.#changeState(REJECTED, reason); }; try { executor(resolve, reject); } catch (error) { reject(error); } }
#changeState(newState, result) { if (this.#state !== PENDING) return; this.#state = newState; this.#result = result; this.#run(); }
#run () {
}
then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => { this.#handlers.push({ onFulfilled, onRejected, resolve, reject, }); this.#run(); }) } }
const p = new MyPromise((resolve, reject) => { setTimeout(() => { resolve("success"); }, 1000); });
p.then((data) => { console.log(data); });
|
好,接下来我们只需要考虑#run方法应该如何写了。首先我们肯定要判断当前promise的状态,如果当前promise是pending状态,就什么都不用做,直接返回。如果当前promise是fulfilled的状态,则让handlers中的对象依次出队并调用onFulfilled方法。如果当前promise是rejected的状态,则调用onRejected方法。
但是这里要注意,因为onFulfilled和onRejected是使用者传递给我们的回调,它可能是一个函数,可能是其他任何东西,甚至可能根本没有传递。所以我们一定要判断其类型后再执行。
于是我们可以写出如下代码:
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
| const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected";
class MyPromise { #state = PENDING; #result = undefined; #handlers = [];
constructor(executor) { const resolve = (data) => { this.#changeState(FULFILLED, data); }
const reject = (reason) => { this.#changeState(REJECTED, reason); }
try { executor(resolve, reject); } catch(err) { reject(err); } }
#run() { if (this.#state === PENDING) return; while (this.#handlers.length) { const {onFulfilled, onRejected, resolve, reject} = this.handlers.shift(); if (this.#state === FULFILLED) { if (typeof onFulfilled === 'function') { onFulfilled(this.#result); } } else { if (typeof onRejected === 'function') { onRejected(this.#result); } } } }
#changeState(newState, result) { if (this.#state !== PENDING) return; this.#state = newState; this.#result = result; this.#run(); }
then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => { handlers.push({onFulfilled, onRejected, resolve, reject}); this.#run(); }) } }
|
现在第一个问题解决了,即什么时候执行onFulfilled和onRejected,有两种情况,一种是then方法中执行,并且此时promise的状态不是pending,还有一种情况是状态切换时执行。所以我们在then方法中即使无法执行也要将回调注册。放入handlers数组中存储。
那么现在就剩下第二个问题,就是什么时候resolve和reject,换句话说,then方法返回的promise状态什么时候改变呢?
有三种情况:
- 对应的回调
onFulfilled或onRejected不是函数:进行状态穿透,then方法返回的promise状态和当前promise的状态要一致。所以我们把#run调整如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #run() { if (this.#state === PENDING) return; while (this.#handlers.length) { const { onFulfilled, onRejected, resolve, reject } = this.#handlers.shift(); if (this.#state === FULFILLED) { if (typeof onFulfilled === "function") { onFulfilled(this.#result); } else { resolve(this.#result); } } else { if (typeof onRejected === "function") { onRejected(this.#result); } else { reject(this.#result); } } } }
|
- 对应的回调
onFulfilled和onRejected是函数,则运行该函数,如果该函数运行过程中没有报错,那么then方法返回的promise就是成功的,成功的值就是onFulfilled的返回值。如果该函数运行过程中报错了,那么then方法返回的prmise就是失败的,失败的值就是onRejected的返回值。
所以我们接着对#run进行修改处理如下:
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
| #run() { if (this.#state === PENDING) return; while (this.#handlers.length) { const { onFulfilled, onRejected, resolve, reject } = this.#handlers.shift(); if (this.#state === FULFILLED) { if (typeof onFulfilled === 'function') { try { const data = onFulfilled(this.#result); resolve(data); } catch (err) { reject(err); } } else { if (typeof onRejected === 'function') { try { const data = onRejected(this.#result); resolve(data); } catch (err) { reject(err); } } } } } }
|
观察不难发现,我们在代码中又出现了重复的语句,所以可以把它再提取为一个函数。#runOne
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #runOne(callback, resolve, reject) { if (typeof callback !== "function") { const settled = this.#state === FULFILLED ? resolve : reject; settled(this.#result); return; } else { try { const data = callback(this.#result); resolve(data); } catch (error) { reject(error); } } }
|
于是我们可以简化#run为如下代码
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
| #runOne(callback, resolve, reject) { if (typeof callback !== "function") { const settled = this.#state === FULFILLED ? resolve : reject; settled(this.#result); return; } try { const data = callback(this.#result); resolve(data); } catch (error) { reject(error); } }
#run() { if (this.#state === PENDING) return; while (this.#handlers.length) { const { onFulfilled, onRejected, resolve, reject } = this.#handlers.shift(); if (this.#state === FULFILLED) { this.#runOne(onFulfilled, resolve, reject); } else { this.#runOne(onRejected, resolve, reject); } } }
|
onFulfilled和onRejected返回的结果是一个promise。在这种情况下,then返回的promise的状态需要和该promise保持一致。
那首先第一个问题就是如何判断一个东西是不是promise呢?很多同学可能会想到用instanceof MyPromise来判断,但实际上这是不够准确的。
因为判断一个东西是不是promise,实际上只需要其满足promiseA+规范即可。比如官方的Promise实例对象,应该是能够和我们自己写的MyPromise实例对象进行互操作的。
所以我们需要一个辅助函数#isPromiseLike来判断一个对象是否满足promiseA+规范。
我们先不写具体实现,假设现在这个#isPromiseLike函数已经写好了,我们来看#runOne函数应该怎么写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #runOne(callback, resolve, reject) { if (typeof callback !== "function") { const settled = this.#state === FULFILLED ? resolve : reject; settled(this.#result); return; } try { const data = callback(this.#result); if (this.#isPromiseLike(data)) { data.then(resolve, reject); } else { resolve(data); } } catch (error) { reject(error); } }
|
到这一步,感觉都差不多了,但是还有一个问题,就是我们都知道官方的promise的ten方法的回调函数是要放入微队列中去执行的,而目前我们的then方法回调是直接执行的,者显然不符合要求。所以我们需要一个辅助函数,#runMicroTask,该函数接收一个参数,然后将参数的函数放入微队列中。我们先用setTimeout的宏任务队列进行模拟。
1 2 3
| #runMicroTask(func) { setTimeout(func, 0); }
|
然后我们需要将#runOne中的所有代码放入微任务队列中,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #runOne(callback, resolve, reject) { this.#runMicroTask(() => { if (typeof callback !== "function") { const settled = this.#state === FULFILLED ? resolve : reject; settled(this.#result); return; } try { const data = callback(this.#result); if (this.#isPromiseLike(data)) { data.then(resolve, reject); } else { resolve(data); } } catch (error) { reject(error); } }) }
|
好,到目前为止,我们只剩下了两个问题,一个是isPromiseLike具体如何实现,另一个是#runMicroTask具体如何实现。我们一个一个来看。
1 2 3 4 5 6
| isPromiseLike(data) { if (data !== null && typeof data === 'function' || typeof data ==='object') { return typeof data.then === 'function'; } return false; }
|
我们要实现promise的互操作性,就需要判断该数据是一个函数或者对象,不为空值,并且有一个then方法,是一个函数。
接下来就是要实现把一个任务放入微队列的方式了,这里我们可以参考vue的源码。在把一个任务放入微任务队列中时采用的方式要看当前的运行时环境。
如果是现在浏览器和Node11+,那么原生就有方法queueMicrotask()可以实现把回调函数放入微队列,如果不支持的话,浏览器环境可以降级为MutationObserver,Node环境可以降级为nextTick。如果还是不支持,只能降级为用setTimeout的宏任务队列来模拟。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #runMicroTask(func) { if (queueMicrotask) { queueMicrotask(func); return; } if (typeof process === 'object' && typeof process.nextTick === 'function') { process.nextTick(func); }else if (typeof MutationObserver === 'function') { const ob = new MutationObserver(func); const textNode = document.createTextNode('1'); ob.observer(textNode, { characterData: true }); textNode.data = '2'; } else { setTimeout(func, 0); } }
|