最近看了些javascript里面Promise关键字的内部实现的文章,也试着自己实现了一下Promise,在这里做一些相关记录。
Promise是一个可以在未来某个时间点返回异步操作结果的对象。这个结果可以是成功resolved解析得到的值,也可以是resovled解析过程中出错的原因。它在执行过程中有3种状态:
- Fulfilled 完成并正确解析结果状态(调用resolve())
- Rejected 完成但是结果解析错误状态 (调用reject())
- Pending 既没有完成也没有拒绝
Promise内部则是通过定义一组状态机的方式来管理三个生命周期的切换,所以第一步先定义状态机的基本结构
function PromiseDemo() {
this.PENDING = 0;
this.FULFILLED = 1;
this.REJECTED = 2;
this.handlers = [];
this.state = this.PENDING; // 初始化Promise的状态,最初的状态为PENDING
this.value = null; // 存储状态变为FULFILLED或者REJECTED之后的值
const fulfilled = (result) => {
this.state = this.FULFILLED;
this.value = result;
};
const rejected = (error) => {
this.state = this.REJECTED;
this.value = error;
};
}
现在已经定义好了Promise的状态机,对于Promise来说,当它的状态不是pending的时候说明它已经处于完成状态(已经被resolve或者rejected了)。一旦Promise的状态从pending进行了切换(调用resolve或者reject),那么它将不会再被改变,这时再次调用resolve或者reject是没有效果的。这种保持完成状态的稳定性是Promise重要的一个特性。
标准Promise定义的实现是通过Promises/A+ specification
)社区制定的规范,简单概括一下Promise的实现大概需要遵从以下的规则:
- 一个Promise是一个能够提供符合标准
.then()
方法的对象 - pending状态的Promise可以过渡到fulfilled或者rejected状态
- fulfilled或者rejected状态的Promise完成以后不能再过度到任何其他状态
- 一旦Promise执行完成,它必须要有一个值(可能是undefined),这个值不能被改变
遵从这几个原则,在定义Promise状态机的时候便定义了它的三个状态,以及过度的到fulfilled
和rejected
的方法fulfill()
和reject()
现在过渡Promise状态的方法有了,那么对于调用的人来说它是在什么地方进行更改的呢?先来看一个使用Promise的例子
const wait = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello');
},0)
})
}
wait().then((result) => {
console.log(result); // hello
})
在wait函数中有setTimeout这样的异步操作,通过使用Promise去包装这个异步操作,然后当里面的异步操作结束之后调用resolve
或者reject
来获取异步操作结束后的值。所以这里是通过实例化Promise时传入的回调来触发状态更改的。现在来给传入的这个回调定义resolve和reject
/**
*@params { fn } promise 实例化传入的回调用来接收resolve和reject
*/
function PromiseDemo(fn) {
this.PENDING = 0;
this.FULFILLED = 1;
this.REJECTED = 2;
this.handlers = []; // 存放.then里面的success或者failure的异步操作
this.state = PENDING; // 初始化Promise的状态,最初的状态为PENDING
this.value = null; // 存储状态变为FULFILLED或者REJECTED之后的值
/**
* 执行.then()两个onFulfilled和onRejected异步处理操作的执行函数
*/
const handler = (handle) => {
if (this.state === this.PENDING) {
this.handlers.push(handle);
} else {
if (
this.state === this.FULFILLED &&
typeof handle.onFulfilled === "function"
) {
handle.onFulfilled(this.value);
}
if (
this.state === this.REJECTED &&
typeof handle.onRejected === "function"
) {
handle.onRejected(this.value);
}
}
};
const fulfilled = (result) => {
this.state = this.FULFILLED;
this.value = result;
this.handlers.forEach(handler);
this.handlers = null;
};
const rejected = (error) => {
this.state = this.REJECTED;
this.value = error;
this.handlers.forEach(handler);
this.handlers = null;
};
function resolve(value) {// fulfill执行过程中如果发生错误,则需要切换到REJECTED状态
try {
fulfilled(value);
}catch(err) {
rejected(err);
}
}
// 传入外部调用Promise时的resolve和reject方法
fn( (result) => {
try {
fulfilled(result);
} catch (error) {
rejected(error);
}
},
(error) => {
rejected(error);
})
this.done = (onFulfilled, onRejected) => {
// 确保结果处理操作和Promise内部的异步操作保持异步
setTimeout(() => {
handler({ onFulfilled,onRejected })
}, 0)
}
这里用setTimeout是为了保证Promise内部的操作都是异步,也就是说当我们在Promise内部传入的没有异步操作时也能保证它异步输出,但是一般没有异步操作也不会使用Promise
这里实现的.done
方法主要是给.then
方法使用。.then
做的事情和.done
是一样的,都是为了输出异步操作的结果,只是.then
在执行的时候会在进程里面重新构造一个Promise。我们通常会这样调用.then
promise.then(
onFulfilled?: Function,
onRejected?: Function
) => Promise
根据Promises/A+的实现,.then
需要遵从以下规则:
-
onFulfilled()
和onRejected()
是可选参数 - 如果
onFulfilled
或者onRejected()
不是函数,它们将会被忽略掉 -
onFulfilled()
会在Promise的状态为fulfilled的时候调用,并使用Promsie异步操作的value
作为它的第一个参数,它不能在Promise还没有过渡到fulfilled状态之前被调用 -
onRejected()
会在Promise的状态为rejected的时候调用,并使用过渡到rejected的异常原因作为第一个参数,它不能在Promise还没有过渡到rejected状态之前被调用 -
onFulfilled()
和onRejected()
都不能调用超过一次 -
.then()
可以在一个Promise里面被调用很多次,当Promise处于fulfilled状态,所有各自的onFullfilled()
回调都必须按照它们在.then
调用里面的顺序执行。同样当Promise处于rejected状态,所有各自的onRejected()
回调必须按照它们在.then
调用里面的顺序执行 -
.then()
必须要返回一个PromisePromise2 = Promise1.then(onFulfilled, onRejected)
- 如果
onFulfilled()
或者onRejected()
返回一个x
值,并且x
是一个Promise, 那么Promise2将会和x
的状态以及value
保持一致,否则Promise2会以x
的值切换到fulfilled状态 - 如果
onFulfilled()
或者onRejected()
抛出一个异常e
,promise2必须过渡到rejected状态,并且使用e
作为理由 - 如果
onFulfilled()
不是一个函数并且promise1过渡到fulfilled状态,promise2必须要使用和promise1同样的值过渡到fulfilled状态
- 如果
- 如果
onRejected()
不是一个函数并且promise1过渡到rejected状态,promise2必须要使用和promise1同样的异常原因过渡到rejected状态
遵从以上.then()
的原则,现在先来简单实现一下.then()
this.then = (onFulfilled, onRejected) => {
return new Promise((resolve, reject) => {
this.done((result) => {
if(typeof onFulfilled === 'function') {
// 这里使用这么多的回调是为了在结果处理的回调里面能够拿到异步操作的结果值
try {
resolve(onFulfilled(result));
}catch(err) {
reject(err);
}
}else {
resolve(result);
}
}, (err) => {
if(typeof onRejected === 'function') {
try {
resolve(onRejected(err));
}catch(ex) {
reject(ex);
}
}else {
reject(err);
}
})
})
}
上面的Promise还有一个地方没有实现,就是在.then
里面有新的异步操作,又使用Promise去包装操作之后,在下一个.then()
里面需要接收到这个异步操作的结果。也就是上面提到的.then()
原则的倒数第二条。首先需要将fn()
的调用封装一下,同时需要添加一个getThen()
的辅助函数用于获取.then()
里面的异步操作
/**
* 检查value值是不是Promise,如果是的话返回这个promise的.then方法
*
*/
const getThen = (value) => {
const t = typeof value;
if (t && (t === "object" || t === "function")) {
const then = value.then;
if (typeof then === "function") {
console.log('functionThen', then);
return then;
}
}
return null;
};
const doResolve = (fn, onFulfilled, onRejected) => {
try {
fn(
(result) => {
try {
onFulfilled(result);
} catch (error) {
onRejected(error);
}
},
(error) => {
onRejected(error);
}
);
} catch (error) {
onRejected(error);
}
};
const resolve = (result) => {
try {
const then = getThen(result);
if (then) {
doResolve(then, resolve, rejected);
return;
}
fulfilled(result);
} catch (error) {
rejected(error);
}
};
完整代码实现:
function MyPromise(fn) {
this.PENDING = 0;
this.FULFILLED = 1;
this.REJECTED = 2;
this.handlers = [];
this.state = this.PENDING;
this.value = null;
const handler = (handle) => {
if (this.state === this.PENDING) {
this.handlers.push(handle);
} else {
if (
this.state === this.FULFILLED &&
typeof handle.onFulfilled === "function"
) {
handle.onFulfilled(this.value);
}
if (
this.state === this.REJECTED &&
typeof handle.onRejected === "function"
) {
handle.onRejected(this.value);
}
}
};
const fulfilled = (result) => {
this.state = this.FULFILLED;
this.value = result;
this.handlers.forEach(handler);
this.handlers = null;
};
const rejected = (error) => {
this.state = this.REJECTED;
this.value = error;
this.handlers.forEach(handler);
this.handlers = null;
};
/**
* 检查value值是不是Promise,如果是的话返回这个promise的.then方法
*
*/
const getThen = (value) => {
const t = typeof value;
if (t && (t === "object" || t === "function")) {
const then = value.then;
if (typeof then === "function") {
console.log('functionThen', then);
return then;
}
}
return null;
};
const doResolve = (fn, onFulfilled, onRejected) => {
try {
fn(
(result) => {
try {
onFulfilled(result);
} catch (error) {
onRejected(error);
}
},
(error) => {
onRejected(error);
}
);
} catch (error) {
onRejected(error);
}
};
const resolve = (result) => {
try {
const then = getThen(result);
if (then) {
doResolve(then, resolve, rejected);
return;
}
fulfilled(result);
} catch (error) {
rejected(error);
}
};
doResolve(fn, resolve, rejected);
this.done = (onFulfilled, onRejected) => {
// 确保promise内部的操作都是异步
setTimeout(() => {
handler({ onFulfilled, onRejected });
}, 0);
};
this.then = (onFulfilled, onRejected) => {
return new MyPromise((resolve, reject) => {
return this.done(
(value) => {
if (typeof onFulfilled === "function") {
// 这里使用这么多的回调是为了在结果处理的回调里面能够拿到异步操作的结果值
try {
return resolve(onFulfilled(value));
} catch (error) {
return reject(error);
}
} else {
return resolve(value);
}
},
(error) => {
if (typeof onRejected === "function") {
try {
return resolve(onRejected(error));
} catch (error) {
return reject(error);
}
} else {
return reject(error);
}
}
);
});
};
}
exports.myPromise = MyPromise;
总结
- Promise内部通过定义状态机来实现pending, fulfilled, rejected三中状态的过渡。pending状态为处理内部异步操作,fulfilled和rejected为异步操作结束后输出处理成功的结果,以及失败的原因
- Promise需要具有符合PromiseA+规范的标准
.then()
方法,用于对Promise异步输出的结果进行处理。
参考文章
- https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-promise-27fc71e77261
- https://promisesaplus.com/
- https://www.promisejs.org/implementing/