/* =====================版本二========================== */
/**
* 版本二
* 可以发现,版本一中的ES5的回调写法在业务复杂之后会形成回调地狱,难以维护的。
* 版本二来初步探讨Promise的实现功能,
* 为什么Promise能够解决或者是说极大的减轻了这种回调地狱
*/
/**
* 先来看Promise利用的原理:利用new操作符,自动执行constructor函数
*
* 毫无疑问的,Person的constructor函数是在new时候自动执行的
* 给constructor函数传入一个参数fn,然后执行fn
*
* 结合实例来看,new Person(foo),将foo作为参数传进去,然后foo会被执行
*
* 以下的代码为了方便,统一使用一个Person类来代替,就不重复写Person了
*/
/**
* foo传入Person后,会在Person的constructor函数中自动的被调用执行
*/
class Person {
constructor(fn) {
fn()
}
}
function foo() {
console.log('foo执行了')
}
new Person(foo)
/**
* 第一步
* 改造一下foo和Person的位置联系,省略写Person了,因为都是一样的流程
*/
new Person(function foo() {
console.log('foo执行了')
})
/**
* 第二步
* 再次改造一下,因为传入的就是一个函数,使用 匿名函数 或者再到 箭头函数 即可
* 把函数foo变为一个箭头函数,实现的功能还是一样的
*/
new Person(() => {
console.log('foo执行了')
})
/**
* 第三步
* 如果在原有的业务逻辑代码上,将这些业务逻辑代码放在传入的函数foo(改造为箭头函数)中
* 因为传入的函数foo会被自动的执行,那么foo函数里面的业务逻辑代码也同样会被执行
*
* console.log('业务逻辑代码块!') 代表业务逻辑
* 可以等效为
*/
// console.log('业务逻辑代码块!')
new Person(function foo() {
/**
* 业务逻辑代码块区域,用一行表示很多的业务逻辑
*/
console.log('业务逻辑代码块!')
})
/**
* 第四步
* 一般认为处理这些逻辑用在是异步任务上,当然同步任务也是可以的
* 将异步任务代码块放入foo中,实现的效果还是和原来直接执行一样,
* 因为foo会被自动的执行,所以将原有的代码块放入foo中,同样的也会被执行
*/
new Person(function foo() {
/**
* 原有的含有异步任务的代码块放入函数foo中,和直接执行是一样的效果
* new自动调用的constructor函数是一个同步任务的过程!不是异步的过程
* 和原有的直接执行异步任务一样,
* new在调用的过程中,自动触发了要执行了这些异步任务的代码块,
* 这些异步任务就被JS引擎放入了异步任务队列。
*
* 用一个简单的定时器表示复杂的异步任务逻辑,如网络请求,IO操作等。
*/
setTimeout(() => { // 异步任务代码块
console.log('异步任务执行')
},1000)
})
/**
* 第五步
* 完成了相同功能的实现后,
* 还要想着
* 仅仅只这样无法完成业务后续的逻辑功能,即成功了执行哪些逻辑,失败了执行哪些逻辑
* 原来还要传入成功的回调和失败的回调,这两个回调函数参数怎么设置。
*
* 优化的逻辑是不能将成功回调success或者失败回调fail传入原有的代码中,
* 而是将异步任务和两个回调任务分开实现,否则还是会形成回调地狱。
*
* 那么如何实现呢?这里必然要有一个信息来通知异步任务执行成功还是执行失败
*
* promise利用了两个信号resolve和reject
* 这两个是函数类型的参数,是什么类型不重要,可以将这两个参数看成是通讯员
* 一个通知异步任务执行成功了,一个通知执行失败了,而函数刚好有这个功能(进行调用)
*
* 在异步任务中进行判断结果,只有在异步任务中才能判断
* 如果成功了就用resolve通知,如果失败了就用reject通知
*/
new Person(function foo() {
/**
* 原有的含有异步任务的代码块放入函数foo中,和直接执行是一样的效果
* 用一个简单的定时器表示复杂的异步任务逻辑,如网络请求,IO操作等。
*
* 注意:一定是在异步任务中进行判断异步任务成功还是失败!
*/
setTimeout(() => { // 异步任务代码块
console.log('异步任务执行')
// 判断成功还是失败 resolve(full filed) or reject
}, 1000);
})
/**
* 第六步
* resolve和reject两个参数怎么放,哪里传入比较合适
* 思考:因为要在异步任务中判断,
* 如果成功就用resolve通知,如果失败就用reject通知
* 所以异步任务必然要能够读取到这两个参数,
* 而再编写代码传入时,传入的只有foo函数,并且异步代码块也是在foo里面,
* 所以放在foo函数的参数里面。
*
* Promise规定第一个参数就是resolve,第二个参数就是reject
* 这样定义就是规范的制定,就是规定好第一个参数是什么,第二个参数是什么,
* 规范制定好了,就不用在查看文档检查参数是什么了。
*
* 传入参数resolve,reject
*/
new Person(function foo(resolve, reject) {
/**
* 原有的含有异步任务的代码块放入函数foo中,和直接执行是一样的效果
* 用一个简单的定时器表示复杂的异步任务逻辑,如网络请求,IO操作等。
*
* 注意:一定是在异步任务中进行判断异步任务成功还是失败!
*/
setTimeout(() => { // 异步任务代码块
console.log('异步任务执行')
// 判断成功还是失败 resolve(full filed) or reject,假设成功
// resolve()
}, 1000);
})
/**
* 第七步
* 因为传入的参数是resolve,reject,但是没有相应的传入
* 所以第六步中的resolve和reject还是一个undefined,如果写成resolve()那么肯定会报错
*
* 怎么解决呢?
* 思考一个问题:resolve和reject是我们传入呢还是直接在里面定义好?
* resolve和reject的作用就是传递信息,通知异步任务是否成功执行,
* 成功了用resolve通知,失败了用reject通知。
*
* 怎么通知呢?
* 用函数调用发送信息,resolve()或reject()
*
* 这也要求必须resolve和reject要和Person关联在一块,
*
* 因为之前说过,异步任务回调的后续(成功还是失败)应该和异步任务分开实现
* 用resolve来通知成功,那么也就是说如果resolve就是成功回调函数success,
* 代码还是和ES5一样,会形成回调地狱,同样的道理,reject也不能是失败回调fail
*
* 那么怎么分开实现呢?
* 还记得第一次用new自动调用constructor函数吗?
* 利用了将函数A作为参数传入另外一个会自动执行的函数B里面,
* 然后利用函数B能够执行的现象,调用了函数A,不就是这套逻辑吗?
*
* 现在在异步任务中判断异步任务是否成功了,决定用resolve还是reject
* resolve或reject可以被调用,也就是能够执行
* 按照上面的推理,应该将成功回调success放入resolve的参数中,将失败回调fail放入reject中
*
* 但是传入参数的时候,foo中传入的resolve是一个函数地址,不可能传入resolve(success)这种形式,
* resolve(success)就表示执行resolve了,不现实。同时上面也说过resolve不能是success
* resolve只能是信息传递的功能,要符合单一功能原则。reject同理
*
* 那么怎么做才能够满足,resolve和reject只是信息传递员,又能够通知到外面呢?
* 别忘记了,功能的实现一个函数A作为参数传入另一个能够被激活执行的函数B中,B中执行了A。
*
* 以resolve为例
* 既然resolve能够被激活执行,同时resolve中不能够承担success的调用。
* 并且异步任务归异步任务的执行,后续的回调应该分开,那么利用一下对象的方法吧。
* 互联网的解决方案永远都是加一层。这里就加一层方法。
*
* 还是以resolve为例
* Promise的实现就是加一个then方法,用resolve调用then方法,
* 然后then方法能够执行了,满足要求添加了,那么将success传进去,就满足了自动调用了
* 并且因为是对象的方法,和new的过程完全是分开的。
*
* 这里将Person改为HyPromise,foo函数改为executor函数,并且给类添加then方法
* 这里还需要理解ES6的类转function 函数对象,类中的非静态方法都是给对象使用的
*/
class HyPromise1 {
constructor(executor) {
executor()
}
then(success) {
success()
}
}
new HyPromise1(function executor(resolve, reject) {
/**
* 用一个简单的定时器表示复杂的异步任务逻辑,要在异步任务中判断是否成功。
*/
setTimeout(() => { // 异步任务代码块
console.log('异步任务执行')
// 判断成功还是失败 resolve(full filed) or reject,假设成功
// resolve()
}, 1000);
})
/**
* 第八步
* 发现resolve reject还是不能调用,因为还不知道resolve reject哪里定义
*
* 以resolve为例
* 很简单了:要调用then,那么定义在HyPromise即可,
* 定义在外部不知道怎么通信通知then,并且就算能够调用then,
* 这个方法定义在外面还是很冗余,因为每次一个new HyPromise还要生成 resolve reject方法。
*
* 改造HyPromise
*/
class HyPromise2 {
constructor(executor) {
executor()
}
resolve() { this.then() }
reject() {
//...
}
then(success) {
success()
}
}
new HyPromise2(function executor(resolve, reject) {
/**
* 用一个简单的定时器表示复杂的异步任务逻辑,要在异步任务中判断是否成功。
*/
setTimeout(() => { // 异步任务代码块
console.log('异步任务执行')
// 判断成功还是失败 resolve(full filed) or reject,假设成功
// resolve()
}, 1000);
})
/**
* 第九步
* 还是发现resolve为undefined,因为没有传入东西
* 类定义的方法是类定义的方法,自己传入的还是自己传入的,没有关联起来
*
* 如果别人传入的是res和rej呢,此时应该将传入的参数和方法关联起来
*
* 在哪关联?初始化对象的时候constructor函数中,resolve和reject都是在executor的参数,直接关联即可
*
* 改造HyPromise
*/
class HyPromise3 {
constructor(executor) {
executor(this.resolve, this.reject)
}
resolve() { this.then() }
reject() {
//...
}
then(success) {
success()
}
}
new HyPromise3(function executor(resolve, reject) {
/**
* 用一个简单的定时器表示复杂的异步任务逻辑,要在异步任务中判断是否成功。
*/
setTimeout(() => { // 异步任务代码块
console.log('异步任务执行')
// 判断成功还是失败 resolve(full filed) or reject,假设成功
resolve()
}, 1000);
}).then(() => {
console.log('then');
})
/**
* 第十步
* 会发现无法执行的,并且顺序也是错误的,then被直接调用了
* resolve无法调用then,因为此时的this指向的是window或者是说undefined(node)
* 因为在异步任务中有一个隐式绑定resolve()直接调用了
*
* 因为实现的是class,而不是用function来实现,可以通过箭头函数来解决
* 因为每次new的过程,生成的都是一个对象,而箭头函数无法改变this,并且此时的this也就是当前的对象了
*
* 还要解决的问题是:then会被直接调用
* 其实应该通过一个收集器收集then函数中传入的回调函数,
* 最后resolve 或者 reject 调用的时候才进行调用回调函数
*
* 但是then在调用的时候需要知道当前是在执行中还是在full filed还是rejected
*
* 用一个状态来记录 status
*
*
* 改造HyPromise
*/
class HyPromise4 {
constructor(executor) {
this.status = 'pending'
this.callbacks = []
executor(this.resolve, this.reject)
}
resolve = () => {
this.status = 'resolved'
this.then()
}
reject = () => {
this.status = 'rejected'
}
then = (success) => {
if(success){
this.callbacks.push(success)
}
if(this.status != 'pending')
this.execute()
}
execute = () => {
this.callbacks.forEach(fn => {
fn()
})
}
}
new HyPromise4(function executor(resolve, reject) {
/**
* 用一个简单的定时器表示复杂的异步任务逻辑,要在异步任务中判断是否成功。
*/
setTimeout(() => { // 异步任务代码块
console.log('异步任务执行')
// 判断成功还是失败 resolve(full filed) or reject,假设成功
resolve()
}, 1000);
}).then(() => {
console.log('then');
})
/**
* 十一步
* 原有的Promise实现中,
* then是能够接收两个参数的,一个是成功的回调,一个是失败的回调,
* 完善HyPromise
*/
class HyPromise5 {
constructor(executor) {
this.status = 'pending'
this.success_callbacks = []
this.fail_callbacks = []
executor(this.resolve, this.reject)
}
resolve = () => {
this.handler('resolved')
}
reject = () => {
this.handler('rejected')
}
handler = (state) => {
/* 只有pending状态才可以变化 */
if(this.status === 'pending') {
this.status = state
switch (this.status) {
case 'resolved':
this.execute(this.success_callbacks)
break;
default:
this.execute(this.fail_callbacks)
}
}
// ... 其他情况不执行
}
then = (success, fail) => {
if(success){
this.success_callbacks.push(success)
}
if(fail) {
this.fail_callbacks.push(fail)
}
}
execute = (callbacks) => {
callbacks.forEach(fn => {
fn()
})
}
}
new HyPromise5(function executor(resolve, reject) {
/**
* 用一个简单的定时器表示复杂的异步任务逻辑,要在异步任务中判断是否成功。
*/
setTimeout(() => { // 异步任务代码块
console.log('异步任务执行')
// 判断成功还是失败 resolve(full filed) or reject,假设成功
reject()
resolve()
}, 1000);
}).then(() => {
console.log('success then');
}, ()=> {
console.log('fail then')
})
/**
* 十二步
* 可以发现第一版最初版本的功能都已实现,
* 但是Promise支持链式调用,并且这个最初的版本还是有很多问题,比如说当错误发生时的处理等
*/
前端Promise实现(二) | 初级版本的实现
最新推荐文章于 2022-07-05 11:17:55 发布