废话少说,直接上代码:
/*
1、定义 MyProObj 的构造函数,构造行数函数接收一个执行方法
工作原理:
给当前对象设置两个方法
then 方法 - 用来注册成功时的回调函数
catch 方法 - 用来注册拒绝时的回调函数
定义 resolve 方法 - 用来延时执行 then 调用时注册的回调函数
*/
function MyProObj(executorFn){
var resolveFn = null;
var rejectFn = null;
/*
定义构造函数的 then 方法
用途:用来在调用时才延迟注册回调函数
工作原理:调用 then 时传入一个回调函数 sucFn 赋值给内部定义的 resolveFn
实现过程:
1、需要接受两个参数,一个成功决议要执行的回调函数 onResolve,一个是失败报错要执行的函数 onReject
*/
this.then = function(onResolve,onReject){
console.log('4、调用 then 方法,将回调函数注册到内部定义的 resolveFn 中,让其后续 resolve 中创建的微任务中调用');
resolveFn = onResolve;
rejectFn = onReject;
}
/*
resolve 方法
用途:用来执行 then 方法注册的回调函数
工作原理:通过 queueMicrotask 创建一个微任务,让回调函数在 then 注册之后才执行
*/
function resolve(val){
console.log('3、执行自定义的 resolve 方法了,但是我得让 then 注册了回调函数之后再执行该回调函数,否则会报错!');
queueMicrotask(()=>{
if(typeof resolveFn === 'function'){
console.log('5、已经调用 then 方法注册过回调函数了,我就愉快的执行注册的回调函数吧!');
resolveFn(val);
}
})
}
console.log('1、实例化 MyProObj 对象时先执行一遍构造函数内部的宏任务');
executorFn(resolve,null);
}
function executorFn(resolve,reject){
if(true){
console.log('2、给想要实例化 MyProObj 的人在合适的时候设置成功的回调');
resolve('成功啦!');
}else{
console.log('给想要实例化 MyProObj 的人在合适的时候设置失败的回调');
reject('被拒绝了呜呜呜~');
}
}
/* 实例化MyProObj */
var MyProObj1 = new MyProObj(executorFn);
/* 成功需要绑定的回调函数 */
function sucFn(val){
console.log(val);
}
/* 调用 MyProObj1.then */
// setTimeout(()=>{
MyProObj1.then(sucFn);
// },1000);跟着代码走,可以看到控制台中打印了如下结果:

可以根据打印结果,推断出整个自定义的 Promise 对象的工作流程为:
1、实例化时执行了一遍构造函数 myProObj
2、然后在构造函数的宏任务末尾调用了构造函数传入的 executorFn 方法
3、executorFn 方法执行了内部定义的 resolve 方法,但此时还未调用 then 对回调函数进行注册(resolveFn),故不会执行 resolveFn
4、在当前宏任务中调用 then 方法,给 resolveFn 赋值 (延迟注册回调函数)
这里为什么必须要在当前宏任务中调用呢?下面简单说明下,具体可以深入了解下 Eevent Loop 中的宏任务和微任务。
因为 resolve 方法实现回调延迟绑定的方式是使用了 queueMicrotask 创建的微任务,在当前宏任务执行结束后,下一个宏任务执行开始前会执行里面的方法,如果这里调用 then 方法的是开启了另一个异步宏任务,则不会触发 resolve 回调函数的执行。你可以将 then 的调放在 setTimeout 中执行,看看是否不会执行 resolve 方法。因为 setTimeout 会将另一个宏任务 push 消息队列中,等待当前宏任务执行完毕,再执行当前宏任务的微任务队列(这里的 resolve 方法创建的微任务属于当前宏任务 ),最后才执行 setTimeout 下一个宏任务中的代码。