这是这两天研究GoldenaArcher 大佬的[1w6k 字详细讲解] 保姆级一步一步带你实现 Promise 的核心功能后的一些总结和自己的看法,对于promise的理解。
本文的例子代码来自上述文章,在这里表示感谢~~~
首先
根据使用得知,Promise 有着以下几个特点:
Promise 是一个对象
新建 Promise 的时候传进去一个回调函数
回调函数需要接受两个回调函数 resolve 和 reject 作为参数
resolve 在调用成功时使用
reject 在调用失败后使用
resolve 和 reject 会被用来去修改 Promise 的状态——Promise 的初始状态为 pending,表示待定
resolve 会将 pending 状态更改为 fulfilled
reject 会将 pending 状态更改为 rejected
并且,一旦状态确定后,就无法继续被更改
首先,从基本的使用层面上我们可以看出在promise new的时候我们传入了一个函数,函数有两个参数,这两个参数在promise中被分别对应上promise原型上的resolve方法 和reject 方法,所以我们可以将两个参数作为函数来执行。
const promise = new Promise(function (resolve, reject) {
if (successful) {
resolve(value);
} else {
rejectet(error);
}
});
解释:
successful是一个变量,当为true的时候返回正确的结果value,调用resolve函数,本来只是一个形参,但是在promise构造函数中它给我们变成了成功函数,在这个函数中,也将pending状态改成了resolved状态。
promise的状态:
promise是一个构造函数,它有状态,并且初始状态是pending,只能从pending变成resolved或者rejected;状态变成resolved或者rejected后不能再次进行改变。
promise
在new的时候会传入一个函数,函数有两个参数,分别是以函数调用的形式返回成功结果和失败结果,在promise中,进入constructor会被立即执行
// 常量,用来定义 Promise 中的状态
// 另一方面,使用变量也会有提示,实现起来更加的方便
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class PromiseR {
// 定义 Promise 中的状态,默认为pending
status = PENDING;
// executor/执行器 是传进 Promise中的回调函数
constructor(executor) {
// 这个回调函数会被立即执行,判断是否应该对状态进行更改
executor(this.resolve, this.reject);
}
// 实现 resolve & reject,用来更改 Promise 的状态
// 使用箭头函数可以减少 this 指向造成的问题,将其绑定在 Promise 的实例对象
resolve = () => {
// 只有当状态是 pending 的时候才能修改状态
if (this.status !== PENDING) return;
this.status = FULFILLED;
};
reject = () => {
// 只有当状态是 pending 的时候才能修改状态
if (this.status !== PENDING) return;
this.status = REJECTED;
};
}
在constructor中有我们传入的回调函数executor,相当于我们传入回调函数的两个参数,变成了promise类上的resolve函数和reject函数,当我们执行的时候返回了我们结果和改变了promise的状态。
上面代码只是将状态做了改变,规定状态只能从pending到resolved或者rejected,并且改变之后不能再次改变。
其次
当我们new promise后返回的是一个promise的对象,我们会使用then来接受正确的结果,then函数的简单实用:
const existedFile = readFile('./test.txt');
existedFile.then(
(data) => {
console.log('content: ', Buffer.from(data).toString());
},
(error) => {
console.log(error);
}
);
readFile读取文件是一个用promise封装好的异步方法,所以有then方法,then方法传入了两个参数,分别是两个函数,第一个函数是的形参是正确的结果,第二个函数的形参是报错信息。
由此可见,then 函数有以下的几个特性:
then 函数接收两个参数
第一个在异步函数操作成功时调用
第二个在异步函数操作失败时调用
then 函数必须要有能够分析 Promise 状态的能力,再根据状态去调用 成功回调函数 或是 失败回调函数
then 方法是被定义在原型对象上的:Promise.prototype.then()
then 的成功函数会接受一个成功后的值作为参数,失败后也会接受一个失败的原因作为参数
then
顾名思义,其实在then中传入两个函数,就是为了在promise上的then方法中使用,将正确的结果以回调的形式传出去,方便再then链式调用中使用。所以下面是简单实现,将传入的两个参数分别执行并传入结果值this.value,这个this.value实在resolve方法传入的,然后保存在了promise原型上,在这里将结果以回调函数的形式返回。
// 接收两个回调函数,前者在状态为 fulfilled 时使用,后者在状态为 rejected 时使用
then(successCB, failCB) {
// 判断状态,根据状态去调用合适的回调函数,并且传入对应的值
// value 和 reason 在 resolve 和 reject 部分已经保存到实例属性上了
if (this.status === FULFILLED) {
successCB(this.value);
} else if (this.status === REJECTED) {
failCB(this.reason);
}
}
在promise中是异步的,所以有时候会等待执行
添加异步逻辑
通常情况下会在 Promise 中被调用的是异步函数,下面就是使用 setTimeout 实现的,一个简单的异步函数的例子:
const asyncPromise = new PromiseR((resolve, reject) => {
setTimeout(() => {
resolve('success');
}, 2000);
});
asyncPromise.then((value) => {
console.log('ln90', value); // 没有输出结果
});
因为在then中并没有对promise状态进行更改,一直是pending
if (this.status === FULFILLED) {
successCB(this.value);
} else if (this.status === REJECTED) {
failCB(this.reason);
}
主线程对于函数的调用也是同步的,它会执行对 PromiseR 对象的实例,接着直接调用 then 函数。此时的 setTimeout 会在 Web API 中被调用,一直到约两秒钟后,主线程执行完毕了,setTimeout 的时间到了,它才会通过 消息队列 与 事件循环 机制被压到 执行栈 中。
这个时候就需要将then中传递过来的函数保存起来
successCB = [];
failCB = [];
resolve = (value) => {
// 省略其他函数
// 调用成功函数
this.successCB && this.successCB(this.value);
};
reject = (reason) => {
// 省略其他函数
// 调用失败函数
this.failCB && this.failCB(this.reason);
};
then(successCB, failCB) {
// 判断状态,根据状态去调用合适的回调函数,并且传入对应的值
// value 和 reason 在 resolve 和 reject 部分已经保存到实例属性上了
if (this.status === FULFILLED) {
successCB(this.value);
} else if (this.status === REJECTED) {
failCB(this.reason);
}
// 函数还没有执行完毕,只能等待
else {
// 将两个回调函数的值存储起来
this.successCB = successCB;
this.failCB = failCB;
}
}
在这里增加了一个类似异步的操作,使用定时器来模拟异步操作,然后再then中判断状态,如果状态已经改变,说明可以直接返回,如果没有说明走的判断状态的else,将传进来的两个函数保存了起来,等待异步完成之后,在resolve和reject这两个函数中执行回调函数successCB、failCB 。
const asyncPromise = new PromiseR((resolve, reject) => {
setTimeout(() => {
resolve('success');
}, 2000);
});
asyncPromise.then((value) => {
console.log('ln103', value);
});
这样等待定时器执行完,就可以then中获取到success了
多次then调用,不是链式调用
successCB = [];
failCB = [];
resolve = (value) => {
// 这里改为使用 shift 去将值弹出
// 弹出的值本身就是一个函数,因此可以直接调用
while (this.successCB.length) {
this.successCB.shift()(this.value);
}
};
// 修改参数,让 reject 接收成功后传来的原因
reject = (reason) => {
// 这里改为使用 shift 去将值弹出
// 弹出的值本身就是一个函数,因此可以直接调用
while (this.failCB.length) {
this.failCB.shift()(this.reason);
}
};
then(successCB, failCB) {
resolve 和 reject 部分已经保存到实例属性上了
if (this.status === FULFILLED) {
successCB(this.value);
} else if (this.status === REJECTED) {
failCB(this.reason);
}
else {
// 数组需要通过push将两个回调函数的值存储起来
this.successCB.push(successCB);
this.failCB.push(failCB);
}
}
这里做了一个很巧妙的处理,将所以待执行的then中成功函数和失败函数都收集起来,在resolve和reject中循环执行第一个,一直执行第一个,因为shift数组方法返回的是删除项,这一项是个函数,随之调用,将value传入。
多次调用 then 的测试
const multiplePromise = new PromiseR((resolve, reject) => {
setTimeout(() => {
console.log('success');
resolve('success');
}, 2000);
});
multiplePromise.then((value) => {
console.log('success1'); // success1
});
multiplePromise.then((value) => {
console.log('success2'); // success2
});
将两次then中的成功函数push到了this.successCB中,在异步执行完后,resolve函数中传入value ==success,随后循环执行最先进入的函数,先打印success1随后打印succcess2
实现 then 方法的链式调用
对于这个功能的视线,有以下几个需求:
实现 thenable 功能
也就是在不考虑其他功能的前提下,先完成链式调用的嵌套
判断返回的应该是一个值,还是应该是 Promise 对象
这还需要判断 Promise 对象返回的结果再决定调用 resolve 还是 reject
使用then().then()…的形式,必须上一个then函数调用的返回值属于promsie对象,并且它的状态也是resolved才能走为我们下一个then
class PromiseR {
then(successCB, failCB) {
// 新建一个Promise对象,否则没有东西可以被返回
const thenablePromise = new PromiseR(() => {
// promise 对象需要接受一个立即执行函数
// 本身 then 之中的逻辑也是需要被立即执行的
// 因此可以将原本的逻辑作为 立即执行函数 executor 传入到 Promise 对象中
if (this.status === FULFILLED) {
const thenableValue = successCB(this.value);
resolve(thenableValue);
} else if (this.status === REJECTED) {
const thenableReason = failCB(this.reason);
reject(thenableReason);
} else {
this.successCB.push(successCB);
this.failCB.push(failCB);
}
});
return thenablePromise;
}
}
返回一个promise对象,this指向当前的promise对象,用当前的promise的状态去做判断
then中返回 Promise 代码实现
class PromiseR {
then(successCB, failCB) {
const thenablePromise = new PromiseR((resolve, reject) => {
if (this.status === FULFILLED) {
const thenableValue = successCB(this.value);
resolvePromise(thenableValue, resolve, reject);
} else if (this.status === REJECTED) {
const thenableReason = failCB(this.reason);
resolvePromise(thenableReason, resolve, reject);
} else {
this.successCB.push(successCB);
this.failCB.push(failCB);
}
});
return thenablePromise;
}
}
// 这段函数会被调用2次,所以单独抽离封装到外部会比较好
// 这里的逻辑主要就负责判断是判断传进来的 thenable 对象是 Promise吗,是的话调用 then 函数去处理,否则 直接返回值
const resolvePromise = (thenablePromise, resolve, reject) => {
if (thenablePromise instanceof PromiseR) {
thenablePromise.then(resolve, reject);
} else {
resolve(thenablePromise);
}
};
代码测试:
// chaining then
const thenablePromise = new PromiseR((resolve, reject) => {
resolve('success');
});
const otherPromise = () =>
new PromiseR((resolve, reject) => {
setTimeout(() => {
resolve('otherPromise');
}, 2000);
});
thenablePromise
.then((value) => {
console.log(value, new Date());
return otherPromise();
})
.then((value) => {
console.log(value, new Date());
});
在thenablePromise的then中返回了一个promise对象,所以还是可以继续then
[nodemon] restarting due to changes...
[nodemon] starting `node 03.handwritePromise.js`
success 2021-06-25T07:55:56.594Z
otherPromise 2021-06-25T07:55:58.611Z
[nodemon] clean exit - waiting for changes before restart
时间戳有 2s 左右的差异
Promise 的错误处理,加上try…catch来进行捕获错误,reject()返回出去,改变promise状态;
promise总体还是一直在搞回调函数,调用回调函数,改变promsie状态,返回结果,then方法接受结果调用传入函数,传入正确结果。