Promise的介绍
一,Promise的理解
在JavaScript中,Promise是一种用于处理异步操作的对象。它代表了一个尚未完成但预期在未来某个时间点完成的操作。Promise对象可以处于以下三种状态之一:
Pending(等待):初始状态,既不是成功,也不是失败状态。
Fulfilled(已成功):意味着操作成功完成。
Rejected(已失败):意味着操作失败。
这几种状态的转换需要遵循一定原则, 失败或者成功只能由padding状态转换。
padding -> fulfilled
padding -> rejected
其使用可以链式调用,在await/async没出来之前,优雅的解决了回调地狱,增强了代码的可读性。
doSomething()
.then(function(result) {
return doSomethingElse(result);
})
.then(function(newResult) {
return doThirdThing(newResult);
})
.then(function(finalResult) {
console.log('最终的结果:', finalResult);
})
.catch(function(error) {
console.error('捕获到错误:', error);
});
原理分析
Promise使用了发布订阅模式。一开始我也是很难理解,后面知道是发布订阅模式一切都变的简单了。因为这种模式在生活中随处可见,我们常用的通信软件。订餐,订酒店,打车。都是商家或司机发布他们的服务之后,消费者去订阅这个服务。他们的服务有可能结束有可能在继续,就像酒店没房间了,或者早上没有热奶茶只有冰奶茶的。消费者在订阅的时候就能看到服务的结果,就会影响接下来的行为。
假如打了一个车,这个时候就要等司机师傅开车来接我们,也就是司机师傅在发布他们的服务之后就进入了一个等待状态,等待乘客打车,这个时候相当于于Promise初始的pedding状态。在我们打到车之后,就订阅了这个司机师傅的服务。如果司机师傅就在附近,那么们立即就能上车,就提前进入了fulfilled状态。如果司机师傅不在附近,那我们就需要等待司机师傅开车来接我们。如果因为中途路况不佳,司机师傅来不了,为表歉意主动告诉情况并取消了订单。就相当于这次打车失败,被拒绝了。进入了rejected状态。如果司机师傅如约而来,我们上车。就打车成功了。进入fulfilled状态。本次打车就结束了。
再如果我们上车之后,发现之前的打车地点输错了。需要修改打车地点。那么这个时候,就要和司机师傅协商修改目的地。在我们设定好新目的地之后。就需要坐在车里面等待司机师傅送我们去。这里修改打车目的地就像于重新订阅。而上车之后的过程就相当于重新进入走了一个Promise。发布订阅。这个过程就是链式调用。
源码实现
回到代码层面。就是我们现在要做一件事情,结果要在未来某的时间点抛出。
定义常量
const STATUS_PADDING = 'padding';
const STATUS_FULFILLED = 'fulfilled';
const STATUS_REJECTED = 'rejected';
定义一个类, 构造传参是一个函数, 而入参函数又有两个参数分别是 resolve, reject。定义value和reason属性存储成功和失败的结果
/**
* 自自定义Promise
*/
class CustomPromise {
constructor(executor) {
this.value = null;
this.reason = null;
this.status = STATUS_PADDING;
const resolve = (value) => {
}
const reject = (reason) => {
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
}
在这里里面我们初始化CustomPromise类的时候,要执行的操作是在executor回调函数里,resolve和reject分别把当次执行的成功或失败结果传递出去。
const test2 = new CustomPromise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve('小于零点五')
} else {
reject('大于零点五')
}
}, 1000)
})
我们这个时候已经可以把结果传递出去了,但是状态还没有改变,这个时候状态也要改变一下,并把结果保存下来。
const resolve = (value) => {
if (this.status === STATUS_PADDING) {
this.status = STATUS_FULFILLED;
this.value = value;
}
const reject = (reason) => {
if (this.status === STATUS_PADDING) {
this.status = STATUS_REJECTED;
this.reason = reason;
}
}
拿到结果后就已经完成了发布。别人还不能订阅。需要创建订阅数组来保存订阅方法,以便在在发布的时候通知订阅者结果。
这样在调用resolve和reject的时候就把结果传递给了订阅者。
this.onRejectCallbacks = []
this.onResolveCallbacks = []
const resolve = (value) => {
if (this.status === STATUS_PADDING) {
this.status = STATUS_FULFILLED;
this.value = value;
this.onResolveCallbacks.forEach(fn => fn(value))
}
}
const reject = (reason) => {
if (this.status === STATUS_PADDING) {
this.status = STATUS_REJECTED;
this.reason = reason;
this.onRejectCallbacks.forEach(fn => fn(reason))
}
}
发布部分全部代码
/**
* 自自定义Promise
*/
class CustomPromise {
constructor(executor) {
this.value = null;
this.reason = null;
this.status = STATUS_PADDING;
this.onRejectCallbacks = []
this.onResolveCallbacks = []
const resolve = (value) => {
if (this.status === STATUS_PADDING) {
this.status = STATUS_FULFILLED;
this.value = value;
this.onResolveCallbacks.forEach(fn => fn(value))
}
}
const reject = (reason) => {
if (this.status === STATUS_PADDING) {
this.status = STATUS_REJECTED;
this.reason = reason;
this.onRejectCallbacks.forEach(fn => fn(reason))
}
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
}
订阅部分基础代码
完成发布之后,还需要订阅者订阅结果。这个方法是then方法。新建一个then方法。then方法里有个函数作为参数,分别是执行成功或失败的监听。这这两个回调方法中的内容需要开发者自行处理。我们把这两个方法传递给监听结果的结果执行数组就是算完成了订阅。
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
if (this.status === STATUS_FULFILLED) {
onFulfilled(this.value)
}
if (this.status === STATUS_REJECTED) {
onRejected(this.reason)
}
if (this.status === STATUS_PADDING) {
this.onResolveCallbacks.push(onFulfilled)
this.onRejectCallbacks.push(onRejected)
}
}
简单发布订阅完整代码
如果状态已经不是padding状态,那么就立即把结果传递出去,如果是就把执行方法保存起来,完成订阅。这样一个简单的发布订阅就实现,让们看下完成代码,
/**
* 自自定义Promise
*/
class CustomPromise {
constructor(executor) {
this.value = null;
this.reason = null;
this.status = STATUS_PADDING;
this.onRejectCallbacks = []
this.onResolveCallbacks = []
const resolve = (value) => {
if (this.status === STATUS_PADDING) {
this.status = STATUS_FULFILLED;
this.value = value;
this.onResolveCallbacks.forEach(fn => fn(value))
}
}
const reject = (reason) => {
if (this.status === STATUS_PADDING) {
this.status = STATUS_REJECTED;
this.reason = reason;
this.onRejectCallbacks.forEach(fn => fn(reason))
}
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
if (this.status === STATUS_FULFILLED) {
onFulfilled(this.value)
}
if (this.status === STATUS_REJECTED) {
onRejected(this.reason)
}
if (this.status === STATUS_PADDING) {
this.onResolveCallbacks.push(onFulfilled)
this.onRejectCallbacks.push(onRejected)
}
}
}
测试订阅能力
让我们测试一下功能吧。
功能是ok,我们实现了Promise最基础的能力。
链式调用的实现
只是实现了订阅还是不够的,Promise能够执行链式调用。而目前的能力还不支持。回想上面提到的打车案例,链式调用相当于一个新的Promise执行。所以返回结果要返回一个新的Promise。在新的Promise初始化执行函数里面完成订阅,把结果传递下去。
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
return new CustomPromise((resolve, reject) => {
// todo
if (this.status === STATUS_FULFILLED) {
onFulfilled(this.value)
}
if (this.status === STATUS_REJECTED) {
onRejected(this.reason)
}
if (this.status === STATUS_PADDING) {
this.onResolveCallbacks.push(onFulfilled)
this.onRejectCallbacks.push(onRejected)
}
})
}
新的Promise的resolve和reject并不能确定调用时机,所以我需要和订阅的方法进行封装,让在执行了订阅的结果之后,把结果传递给新的Promise。
const handleFulfilled = (value) => {
try {
const result = onFulfilled(value)
if (result instanceof CustomPromise) {
result.then(resolve, reject)
} else {
resolve(result)
}
} catch (error) {
reject(error)
}
}
const handleRejected = (reason) => {
try {
const result = onRejected(reason)
if (result instanceof CustomPromise) {
result.then(resolve, reject)
} else {
reject(result)
}
} catch (error) {
reject(error)
}
}
封装后的订阅方法
if (this.status === STATUS_FULFILLED) {
handleFulfilled(this.value)
}
if (this.status === STATUS_REJECTED) {
handleRejected(this.reason)
}
if (this.status === STATUS_PADDING) {
this.onResolveCallbacks.push(handleFulfilled)
this.onRejectCallbacks.push(handleRejected)
}
完整链式调用代码
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
return new CustomPromise((resolve, reject) => {
const handleFulfilled = (value) => {
try {
const result = onFulfilled(value)
if (result instanceof CustomPromise) {
result.then(resolve, reject)
} else {
resolve(result)
}
} catch (error) {
reject(error)
}
}
const handleRejected = (reason) => {
try {
const result = onRejected(reason)
if (result instanceof CustomPromise) {
result.then(resolve, reject)
} else {
reject(result)
}
} catch (error) {
reject(error)
}
}
if (this.status === STATUS_FULFILLED) {
handleFulfilled(this.value)
}
if (this.status === STATUS_REJECTED) {
handleRejected(this.reason)
}
if (this.status === STATUS_PADDING) {
this.onResolveCallbacks.push(handleFulfilled)
this.onRejectCallbacks.push(handleRejected)
}
})
}
让们来测试一下吧
测试结果也是ok的。我们已经完成了链式调用。
链式调用Promise完整功能代码
/**
* 自自定义Promise
*/
class CustomPromise {
constructor(executor) {
this.value = null;
this.reason = null;
this.status = STATUS_PADDING;
this.onRejectCallbacks = []
this.onResolveCallbacks = []
const resolve = (value) => {
if (this.status === STATUS_PADDING) {
this.status = STATUS_FULFILLED;
this.value = value;
this.onResolveCallbacks.forEach(fn => fn(value))
}
}
const reject = (reason) => {
if (this.status === STATUS_PADDING) {
this.status = STATUS_REJECTED;
this.reason = reason;
this.onRejectCallbacks.forEach(fn => fn(reason))
}
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
return new CustomPromise((resolve, reject) => {
const handleFulfilled = (value) => {
try {
const result = onFulfilled(value)
if (result instanceof CustomPromise) {
result.then(resolve, reject)
} else {
resolve(result)
}
} catch (error) {
reject(error)
}
}
const handleRejected = (reason) => {
try {
const result = onRejected(reason)
if (result instanceof CustomPromise) {
result.then(resolve, reject)
} else {
reject(result)
}
} catch (error) {
reject(error)
}
}
if (this.status === STATUS_FULFILLED) {
handleFulfilled(this.value)
}
if (this.status === STATUS_REJECTED) {
handleRejected(this.reason)
}
if (this.status === STATUS_PADDING) {
this.onResolveCallbacks.push(handleFulfilled)
this.onRejectCallbacks.push(handleRejected)
}
})
}
}
以上们已经实现了Promise的链式调用。在下一篇中将讲到Promise一些常用方法的实现,让我们一起探索编程的乐趣吧。