一、Promise的定义
根据PromiseA+的定义,Promise会有三个状态,pending、fulfilled、rejected,当它的状态被固定下来,就不会被改变。
二、手写Promise
我们使用Promise的方式,一般如下
const p = new Promise(...)
可见,Promise是通过构造调用的方式来使用,所以Promise其实是个JS提供的内置函数,等同于Array、Boolean之类。
为了方便,这里使用ES6的语法糖————Class的方式来实现。
1.Promise的状态定义
Promise有三个状态,pending、fulfilled和rejected,同时我们定义的状态不想要被Promise的实例继承,所以我们在状态定义时加上了static关键字。
加上static关键字,就表示该属性不会被实例继承,而是直接通过类来调用,这就称为“静态属性”。
class Promise{
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
static REJECTED = 'rejected';
}
2.Promise的构造
回想一下Promise的使用如下
const p = new Promise((resolve,reject)=>{
// someting
})
Promise的构造接受一个函数,该函数的参数是resolve方法和reject方法,所以我们在constructer里需要接收一个函数。
构造一个原生的Promise,可以发现实例上有PromiseState和PromiseResult两个属性,要让new构造返回的对象可以访问到,那么这些属性要能通过原型链访问,则需要通过this.xx定义这两个属性。
class Promise{
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
static REJECTED = 'rejected';
constructor (fn) {
this.PromiseState = Promise.PENDING;
this.PromiseResult = undefined;
fn();
}
}
传递进来的resolve和reject方法其实是个形参,Promise内部需要有自己的设计逻辑,比如说改变Promise的状态并且把结果返回,所以内部需要实现自己的resolve和reject方法,并且在fn的调用时绑定。
JS的this指向是隐藏的坑,这里需要通过bind方法绑定this执行,避免非预期的结果
class Promise{
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
static REJECTED = 'rejected';
constructor (fn) {
this.PromiseState = Promise.PENDING;
this.PromiseResult = undefined;
fn(this.resolve.bind(this),this.reject.bind(this));
}
resolve (value) {
}
reject (reason) {
}
}
3.resolve和reject方法
这两个方法的作用,主要是改变Promise的状态,同时把结果返回
class Promise{
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
static REJECTED = 'rejected';
constructor (fn) {
this.PromiseState = Promise.PENDING;
this.PromiseResult = undefined;
fn(this.resolve.bind(this),this.reject.bind(this));
}
resolve (value) {
if (this.PromiseState === Promise.PENDING) {
this.PromiseState = Promise.FULFILLED;
this.PromiseResult = value;
}
}
reject (reason) {
if (this.PromiseState === Promise.PENDING) {
this.PromiseState = Promise.REJECTED;
this.PromiseResult = reason;
}
}
}
4.then方法
Promise的特点之一就是有then方法。then方法的使用如下:
const p = new Promise((resolve,reject)=>{
resolve("会有好运");
})
p.then(
res =>{
//
},
reason=>{
//
})
可见then方法的参数是两个函数,我们分别命名为onFulfilled和onRejected,分别在成功、失败时调用。
class Promise{
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
static REJECTED = 'rejected';
constructor (fn) {
this.PromiseState = Promise.PENDING;
this.PromiseResult = undefined;
fn(this.resolve.bind(this),this.reject.bind(this));
}
resolve (value) {
if (this.PromiseState === Promise.PENDING) {
this.PromiseState = Promise.FULFILLED;
this.PromiseResult = value;
}
}
reject (reason) {
if (this.PromiseState === Promise.PENDING) {
this.PromiseState = Promise.REJECTED;
this.PromiseResult = reason;
}
}
then (onFulfilled, onRejected) {
if(this.PromiseState === Promise.FULFILLED){
onFulfilled(this.PromiseResult);
}
if(this.PromiseState === Promise.REJECTED){
onRejected(this.PromiseResult);
}
}
}
到这里Promise的属性和方法基本完成了,我们测试一下
resolve和then方法都被执行了,但是这里其实是同步的JS线性执行,而真实场景的Promise在事件循环中是异步执行的,所以我们还需要进一步完善。
三、Promise的完善
1.报错的处理
如果在Promise的构造中有异常,是会直接调用reject将报错信息返回,如
所以,我们在构造时加上try-catch,如有异常,直接reject
constructor (fn) {
this.PromiseState = Promise.PENDING;
this.PromiseResult = undefined;
try { // 这里是新加的
fn(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error);
}
}
2.then方法的完善
是不是认为then方法已经大功告成了,然而并没有。再对照Promise A+规范看一眼
这里说then方法的onFulfilled和onRejected是可选的,如果不是函数,就忽略它。
忽略它,什么意思嘛?
看下我们之前版本忽略onFulfilled会有什么问题,
由此可见,我们需要写兼容逻辑。如果onFulfilled和onRejected不是函数,我们需要自定义兼容函数,同时reject的报错需要透传到外部,所以需要throw一个错误出来。
class Promise{
//...
then (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onFulfilled = typeof onRejected === 'function' ? onRejected : reason => {throw reason};
if(this.PromiseState === Promise.FULFILLED){
onFulfilled(this.PromiseResult);
}
if(this.PromiseState === Promise.REJECTED){
onRejected(this.PromiseResult);
}
}
}
3.异步执行
众所周知,Promise.resolve和Promise.reject并不是同步执行的,而是异步执行。
来看看原生的代码执行顺序
看看我们之前写的代码的执行顺序
需要改成异步来和原生的Promise来保持一致, 用queueMicrotask来实现。
class Promise {
// ...
then (onFulfilled, onRejected) {
if (this.PromiseState === Promise.FULFILLED) {
queueMicrotask(() => {
onFulfilled(this.PromiseResult);
})
}
if (this.PromiseState === Promise.REJECTED) {
queueMicrotask(() => {
onRejected(this.PromiseResult);
})
}
}
}
再测试一下,是想要的顺序了
4.回调保存
想一个问题,then()是异步执行的,如果我们也异步resolve,他们的顺序应该怎样?测试的代码如下:
console.log(1);
const p = new Promise((resolve, reject) => {
queueMicrotask(() => {
resolve(123);
console.log(3);
});
});
p.then(res => {
console.log('then resolve', res)
}, reason => {
console.log('then reject', reason)
})
console.log(2);
原生的Promise执行结果如下:
我们自己写的版本:
可以看到,then方法的输出没有被执行。
原因分析:
- 在创建Promise的时候,我们使用了异步resolve,这里用queueMicrotask创建了一个微任务。
- 在执行
then方法时,因为Promise的状态没有变成fulfilled或者rejected,所以onFulfilled和onRejected都没有执行 - 异步的resolve方法被执行,输出了数字3
我们当下需要改进的是,在resolve之后,then方法中的回调才被执行,所以then方法需要在pending的时候加逻辑,我们需要一个队列,存放变成非pending状态时再执行的函数。
class Promise{
//...
constructor (fn) {
this.PromiseState = Promise.PENDING;
this.PromiseResult = undefined;
this.onFulfilledCallback = []; // 新加的
this.onRejectedCallback = [];// 新加的
try {
fn(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error);
}
}
resolve (value) {
if (this.PromiseState === Promise.PENDING) {
this.PromiseState = Promise.FULFILLED;
this.PromiseResult = value;
this.onFulfilledCallback.forEach((callback) => callback(value));// 新加的
}
}
reject (reason) {
if (this.PromiseState === Promise.PENDING) {
this.PromiseState = Promise.REJECTED;
this.PromiseResult = reason;
this.onRejectedCallback.forEach((callback) => callback(reason));// 新加的
}
}
then (onFulfilled, onRejected) {
if (this.PromiseState === Promise.PENDING) { // 新加的
this.onFulfilledCallback.push(onFulfilled(this.PromiseResult));
this.onRejectedCallback.push(onRejected(this.PromiseResult));
}
//...
}
}
再重新运行测试case,结果如下:
我们发现,then方法被执行了,但是顺序不对,原生的Promise,then方法应该要最后执行。所以,这里回调的队列中也应该存的是异步的回调。
then (onFulfilled, onRejected) {
if (this.PromiseState === Promise.PENDING) {
this.onFulfilledCallback.push(
queueMicrotask(() => onFulfilled(this.PromiseResult))
);
this.onRejectedCallback.push(
queueMicrotask(() => onRejected(this.PromiseResult))
);
}
if (this.PromiseState === Promise.FULFILLED) {
queueMicrotask(() => {
onFulfilled(this.PromiseResult);
})
}
if (this.PromiseState === Promise.REJECTED) {
queueMicrotask(() => {
onRejected(this.PromiseResult);
})
}
}
再运行测试的case,结果如
可见,我们已经成功了一大半啦!
5.then的链式调用
Promise是支持链式调用的,我们用之前写好的Promise版本测试一下。
class Promise {
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
static REJECTED = 'rejected';
constructor (fn) {
this.PromiseState = Promise.PENDING;
this.PromiseResult = undefined;
this.onFulfilledCallback = [];
this.onRejectedCallback = [];
try {
fn(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error);
}
}
resolve (value) {
if (this.PromiseState === Promise.PENDING) {
this.PromiseState = Promise.FULFILLED;
this.PromiseResult = value;
this.onFulfilledCallback.forEach((callback) => callback(value));
}
}
reject (reason) {
if (this.PromiseState === Promise.PENDING) {
this.PromiseState = Promise.REJECTED;
this.PromiseResult = reason;
this.onRejectedCallback.forEach((callback) => callback(reason));
}
}
then (onFulfilled, onRejected) {
if (this.PromiseState === Promise.PENDING) {
this.onFulfilledCallback.push(() => queueMicrotask(() => onFulfilled(this.PromiseResult)));
this.onRejectedCallback.push(() => queueMicrotask(() => onRejected(this.PromiseResult)));
}
if (this.PromiseState === Promise.FULFILLED) {
queueMicrotask(() => {
onFulfilled(this.PromiseResult);
})
}
if (this.PromiseState === Promise.REJECTED) {
queueMicrotask(() => {
onRejected(this.PromiseResult);
})
}
}
}
console.log(1);
const p = new Promise((resolve, reject) => {
queueMicrotask(() => {
resolve(123);
console.log(3);
});
});
p.then(res => {
console.log('then1 resolve', res)
}, reason => {
console.log('then1 reject', reason)
}).then(res => {
console.log('then2 resolve', res)
}, reason => {
console.log('then2 reject', reason)
});
console.log(2);
发现报错了,当前是不支持链式调用的。
要实现链式调用,Promise.prototype.then方法应该要返回一个新的Promise实例,具体的实现逻辑我们看Promise A+ 规范。
翻译一下
2.2.7. then 必须返回一个Promise对象
promise2 = promise1.then(onFulfilled,onRejected)
- 2.2.7.1 如果
onFulfilled或者onRejected中的任意一个返回一个x值,运行Promise的定义过程[[Resolve]](promise2,x)- 2.2.7.2 如果
onFulfilled或者onRejected中的任意一个抛出异常e,promise2需要变成rejected状态并把e当成reason.- 2.2.7.3 如果
onFulfilled不是一个函数且promise1变成了fulfilled,promise2必须是fulfilled的,返回和promise1相同的值.- 2.2.7.4 如果
onRejected不是一个函数且promise1变成了rejected,promise2必须是rejected的,返回和promise1相同的原因。
我们再来看看Promise处理过程的定义
翻译一下
- 2.3 Promise定义过程
Promise定义过程是一个抽象的操作,接收一个promise和一个值作为输入,我们把它表示为
[[Resolve]](promise,x)。如果x是一个thenable的对象,使用x作为promise的状态,假设x的行为至少部分像一个promise。否则,promise变成fulfilled使用x作为值。对thenables这样的处理允许promise实现可交互,只要他暴露符合PromiseA+规范的
then方法。同时允许PromiseA+ 兼容异常的情况作为reasonable的then方法。要运行
[[Resolve]](promise,x),要执行下面的步骤
- 2.3.1 如果
promise和x指向同一个对象,执行rejectpromise并返回TypeError作为原因.- 2.3.2 如果
x是一个promise,采用它的状态
- 2.3.2.1 如果
x是pending状态,promise必须保持pending直到x变成fulfilled或者rejected.- 2.3.2.2 如果
x是fulfilled状态,用相同的value值把promise变成fulfilled.- 2.3.2.3 如果
x是rejected状态,用相同的reason值把promise变成rejected.- 2.3.3 如果
x是个对象或者函数
- 2.3.3.1 让
then变成x.then- 2.3.3.2 如果检索到
x.then返回的结果抛出了一个异常e,rejectpromise将e作为原因- 2.3.3.3 如果
then是一个函数,把x当成this,第一个参数是resolvePromise,第二个参数是rejectPromise,当:
- 2.3.3.3.1 当
resolvePromise被调用时值为y,运行[[Resolve]](promise,y).- 2.3.3.3.2 当
rejectPromise被调用时原因为r,用r作为原因reject.- 2.3.3.3.3 如果
resolvePromise和rejectPromise都被调用,或者同样的参数被调用多次,第一次调用的结果有最高的优先级,后面的调用将会被忽略。- 2.3.3.3.4 如果调用
then的时候抛出了一个异常e
- 2.3.3.3.4.1 如果
resolvePromise或者rejectPromise已经被调用过,就忽略它。- 2.3.3.3.4.2 否则,reject
promise将e作为原因- 2.3.3.4 如果
then不是一个函数,用x作为值fulfillpromise- 2.3.4 如果
x不是一个对象或者函数,用x作为值fulfillpromise.如果promise是resolved的同时thenable是有个一个环状的thenable原型链,这会导致
[[Resolve]](promise,thenable)的递归性质使得[[Resolve]](promise,thenable)被再次调用,依据上面的算法这将会导致无限递归下去,鼓励但不是必须的,去检测这样的递归情况同时返回一个reject的promise,使用TypeError作为原因。
总结一下then方法需要返回一个Promise对象,同时这里会调用一个[[Resolve]](promise,x)的函数。
所以,接下来我们需要依据规范修改then的内部逻辑并实现[[Resolve]](promise,x)函数。
then的内部应该返回一个promise,所以有
then (onFulfilled, onRejected) {
const promise2 = new Promise((resolve, reject) => {
});
return promise2;
}
依据规范完善内部实现then方法,
then (onFulfilled, onRejected) {
const promise2 = new Promise((resolve, reject) => {
if (this.PromiseState === Promise.PENDING) {
this.onFulfilledCallback.push(() => queueMicrotask(() => {
try {
if (typeof onFulfilled !== 'function') {
resolve(this.PromiseResult);
} else {
let x = onFulfilled(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
}
} catch (e) {
reject(e);
}
}
));
this.onRejectedCallback.push(() => queueMicrotask(() => {
try {
if (typeof onRejected !== 'function') {
reject(this.PromiseResult);
} else {
let x = onRejected(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
}
} catch (e) {
reject(e);
}
}));
}
if (this.PromiseState === Promise.FULFILLED) {
queueMicrotask(() => {
try {
if (typeof onFulfilled !== 'function') {
resolve(this.PromiseResult);
} else {
let x = onFulfilled(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
}
} catch (e) {
reject(e);
}
})
}
if (this.PromiseState === Promise.REJECTED) {
queueMicrotask(() => {
try {
if (typeof onRejected !== 'function') {
reject(this.PromiseResult);
} else {
let x = onRejected(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
}
} catch (e) {
reject(e);
}
})
}
});
return promise2;
}
接下来实现resolvePromise(promise2, x, resolve, reject)函数
function resolvePromise(promise2, x, resolve, reject) {
//2.3.1 如果`promise`和`x`指向同一个对象,执行reject `promise`并返回`TypeError`作为原因
if (promise2 === x) {
reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
}
// 2.3.2 如果`x`是一个promise
if (x instanceof Promise) {
x.then(
(y) => resolvePromise(promise2, y, resolve, reject),
(r) => reject(r)
);
}
// 2.3.3 如果`x`是个对象或者函数
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
// 2.3.3.3 如果then是一个函数,把x当成this,第一个参数是resolvePromise,第二个参数是rejectPromise
if (typeof then === 'function') {
let called = false;
try {
//2.3.3.3.3 如果resolvePromise和rejectPromise都被调用,或者同样的参数被调用多次,第一次调用的结果有最高的优先级,后面的调用将会被忽略。
then.call(x,
(y) => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject)
},
(r) => {
if (called) return;
called = true;
reject(r)
});
} catch (e) {
//2.3.3.3.4 如果调用then的时候抛出了一个异常e
//2.3.3.3.4.1 如果resolvePromise或者rejectPromise已经被调用过,就忽略它。
//2.3.3.3.4.2 否则,reject promise将e作为原因
if (called) return;
called = true;
reject(e)
}
} else {
resolve(x);
}
} catch (e) {
//2.3.3.2 如果检索到x.then返回的结果抛出了一个异常e,reject promise将e作为原因
reject(e);
}
} else {
// 2.3.4 如果x不是一个对象或者函数,用x作为值fulfill promise
resolve(x);
}
}
到这里,我们的Promise已经完成了,接下来要做的就是检测是否符合PromiseA+的规范
完整版代码如下:
class Promise {
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
static REJECTED = 'rejected';
constructor (fn) {
this.PromiseState = Promise.PENDING;
this.PromiseResult = undefined;
this.onFulfilledCallback = [];
this.onRejectedCallback = [];
try {
fn(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error);
}
}
resolve (value) {
if (this.PromiseState === Promise.PENDING) {
this.PromiseState = Promise.FULFILLED;
this.PromiseResult = value;
this.onFulfilledCallback.forEach((callback) => callback(value));
}
}
reject (reason) {
if (this.PromiseState === Promise.PENDING) {
this.PromiseState = Promise.REJECTED;
this.PromiseResult = reason;
this.onRejectedCallback.forEach((callback) => callback(reason));
}
}
then (onFulfilled, onRejected) {
const promise2 = new Promise((resolve, reject) => {
if (this.PromiseState === Promise.PENDING) {
this.onFulfilledCallback.push(() => queueMicrotask(() => {
try {
if (typeof onFulfilled !== 'function') {
resolve(this.PromiseResult);
} else {
let x = onFulfilled(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
}
} catch (e) {
reject(e);
}
}
));
this.onRejectedCallback.push(() => queueMicrotask(() => {
try {
if (typeof onRejected !== 'function') {
reject(this.PromiseResult);
} else {
let x = onRejected(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
}
} catch (e) {
reject(e);
}
}));
}
if (this.PromiseState === Promise.FULFILLED) {
queueMicrotask(() => {
try {
if (typeof onFulfilled !== 'function') {
resolve(this.PromiseResult);
} else {
let x = onFulfilled(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
}
} catch (e) {
reject(e);
}
})
}
if (this.PromiseState === Promise.REJECTED) {
queueMicrotask(() => {
try {
if (typeof onRejected !== 'function') {
reject(this.PromiseResult);
} else {
let x = onRejected(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
}
} catch (e) {
reject(e);
}
})
}
});
return promise2;
}
}
function resolvePromise(promise2, x, resolve, reject) {
//2.3.1 如果`promise`和`x`指向同一个对象,执行reject `promise`并返回`TypeError`作为原因
if (promise2 === x) {
reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
}
// 2.3.2 如果`x`是一个promise
if (x instanceof Promise) {
x.then(
(y) => resolvePromise(promise2, y, resolve, reject),
(r) => reject(r)
);
}
// 2.3.3 如果`x`是个对象或者函数
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
// 2.3.3.3 如果then是一个函数,把x当成this,第一个参数是resolvePromise,第二个参数是rejectPromise
if (typeof then === 'function') {
let called = false;
try {
//2.3.3.3.3 如果resolvePromise和rejectPromise都被调用,或者同样的参数被调用多次,第一次调用的结果有最高的优先级,后面的调用将会被忽略。
then.call(x,
(y) => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject)
},
(r) => {
if (called) return;
called = true;
reject(r)
});
} catch (e) {
//2.3.3.3.4 如果调用then的时候抛出了一个异常e
//2.3.3.3.4.1 如果resolvePromise或者rejectPromise已经被调用过,就忽略它。
//2.3.3.3.4.2 否则,reject promise将e作为原因
if (called) return;
called = true;
reject(e)
}
} else {
resolve(x);
}
} catch (e) {
//2.3.3.2 如果检索到x.then返回的结果抛出了一个异常e,reject promise将e作为原因
reject(e);
}
} else {
// 2.3.4 如果x不是一个对象或者函数,用x作为值fulfill promise
resolve(x);
}
}
Promise.deferred = function() {
let result = {};
result.promise = new Promise((resolve, reject) => {
result.resolve = resolve;
result.reject = reject;
});
return result;
}
module.exports = Promise;
// console.log(1);
// const p = new Promise((resolve, reject) => {
// queueMicrotask(() => {
// resolve(123);
// console.log(3);
// });
// });
// p.then(res => {
// console.log('then1 resolve', res)
// }, reason => {
// console.log('then1 reject', reason)
// }).then(res => {
// console.log('then2 resolve', res)
// }, reason => {
// console.log('then2 reject', reason)
// });
// console.log(2);
六、PromiseA+规范检测
1.安装官方的测试工具包
yarn add promises-aplus-tests
2.定义deferred方法
需要注意的是官方这里写着用Node.js modules导出也就是用CommonJS导出。
Promise.deferred = function() {
let result = {};
result.promise = new Promise((resolve, reject) => {
result.resolve = resolve;
result.reject = reject;
});
return result;
}
module.exports = Promise;
3.修改package.json文件
{
// package.json
"dependencies": {
"promises-aplus-tests": "^2.1.2"
},
"scripts": {
"test": "promises-aplus-tests index"
}
}
项目结构如下:
4.运行测试用例
yarn test
结果如:
至此,我们的Promise大功告成啦~
七、总结
在这次的实践中,我们实现了resolve、reject、then、resolvePromise这四个核心的方法。这几个是一个Promise必须含有的方法。
在这个过程中,可以看到如果promise的状态已经是fulfilled或者rejected,是会直接创建新的任务放到浏览器的事件循环中,如果是pending,则会在Promise状态定下来的时候才会建立新的任务加入事件循环。