第一步:列出三大块:this.then, resolve/reject fn(resolve/reject)
第二步:this.then负责注册所有的函数,resolve/reject负责执行所有的函数
第三步:在resolve/reject里面加上setTimeout 防止还没进行then注册,就直接执行resolve了
第四步:resolve/reject里面要返回this, 这样就可以链式调用了
第五步:三个状态的管理:
pending 初始化状态
resolve 成功
reject 失败
promise的链式调用 在then里面return一个promise 这样才能then里面加上异步函数
加上catch
/*
DIYPromisee类必须接收一个函数参数(也就是需要执行异步任务的函数),该函数将在传入之后立即执行,
并传入DIYPromise对象下的两个方法resolve和reject
*/
class DIYPromise {
/*
DIYPromise状态有三种:
pending => 初始状态
resolve =>成功
reject => 失败
每一个DIYPromise对象只能由pending状态变为resolve或者reject状态,且状态一旦发生改变以后就不能再改变了***
*/
static PENDING = "PENDING";
static RESOLVE = "RESOLVE";
static REJECT = "REJECT";
constructor ( handler ) {
if (typeof handler !== 'function') {
throw new TypeError('Promise resolver undefined is not a function');
}
this.status = DIYPromise.PENDING;
this.resolvedQueues = [];
this.rejectedQueues = [];
handler(this._resolve.bind(this), this._reject.bind(this));
}
_resolve () {
/*
将当前promise的状态由pending变为resolve状态,并执行成功后的注册函数
如果当前状态改变过了,则return
*/
console.log('resolve');
if (this.status !== DIYPromise.PENDING) return
this.status = DIYPromise.RESOLVE;
let handler;
while (handler = this.resolvedQueues.shift()) {
handler();
}
}
_reject () {
/*
将当前promise的状态由pending变为reject状态,并执行失败后的注册函数
如果当前状态改变过了,则return
*/
console.log('reject');
if (this.status !== DIYPromise.PENDING) return
this.status = DIYPromise.REJECT;
let handler;
while (handler = this.rejectedQueues.shift()) {
handler();
}
}
then (resolvedHandler, rejectedHandler) {
// 这里传进来的函数不能直接调用,而应该类似推入事件队列里面去
// 理解事件原理
console.log('then');
this.resolvedQueues.push(resolvedHandler);
this.rejectedQueues.push(rejectedHandler);
}
}
/*
截止到目前为止,存在一个问题,
那就是: 如果回调函数里面不是一个定时器,情况是是怎么样呢?
结果是 ==> then()里面的回调没有执行,那么为什么then里面的回调没有执行呢?
原因就是:
这个时候,demo1.then()和resolve()是同步的状态,如何保证是异步的?如何解决这个问题?
let demo1 = new DIYPromise((resolve, reject) => {
console.log('DIYPromise');
resolve();
});
demo1.then(res => {
console.log('res', res );
}, error => {
console.log('出错了', error);
});
*/
那么如何解决上面所说的问题呢?
解决方法:
1、可以在resolve和reject中使用setTimeout();
_resolve () {
/*
将当前promise的状态由pending变为resolve状态,并执行成功后的注册函数
如果当前状态改变过了,则return
*/
setTimeout(() => {
if (this.status !== DIYPromise.PENDING) return
this.status = DIYPromise.RESOLVE;
let handler;
while (handler = this.resolvedQueues.shift()) {
handler();
}
}, 0);
}
_reject () {
/*
将当前promise的状态由pending变为reject状态,并执行失败后的注册函数
如果当前状态改变过了,则return
*/
setTimeout(() => {
if (this.status !== DIYPromise.PENDING) return
this.status = DIYPromise.REJECT;
let handler;
while (handler = this.rejectedQueues.shift()) {
handler();
}
}, 0);
}
为什么使用这个方法可以解决呢?这就涉及到同步任务和异步任务的概念,以及微任务和宏任务
不管定时器写的是多少时间,它都是一个异步任务,那么异步任务肯定要比同步任务执行要再靠后
但是这种方式还是存在问题:看下面一段代码理解一下:如果使用原生的Promise会是怎么样的结果呢?
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve().then(res => {
console.log('then');
});
//那么上面代码是先答应setTimeout还是then呢?
如果使用我们当前的自己写的promise对象的话,又是什么结果呢?
setTimeout(() => {
console.log('setTimeout');
}, 0);
new DIYPromise((resolve) => {
resolve()
}).then(res => {
console.log('then');
});
结果显然,跟原生promise中的结果是不一样的,为什么会这样呢?
原因是,我们当前写的DIYPromise类中的resolve使用的也是setTimeout,那么都是宏任务,那么这个时候,外层的setTimeout函数比resolve中的setTimeout要先注册,那怎么样才能做到像原生那样,能够让then先执行的这种机制呢?这就是微任务。
那么到底什么是宏任务,什么是微任务呢?
原因是js是单线程的,当我们的代码执行的时候,它会把代码放到主线程中去执行,然后把异步任务代码放到异步线程当中去处理,同步代码执行完之后,会进入到一个eventLoop里面去,事件队列里面是不是有函数,事件队列里面分两个,一个叫宏任务队列,一个叫微任务队列,微任务队列的优先级高于宏任务,如果同时存在宏任务和微任务的话,会优先执行微任务里面的任务,然后才执行宏任务队列里面的任务;
那么基于当前的代码下,如何不适用setTimeout,模拟微任务呢?
那么在源生中,有一个方法叫postMessage,可以模拟微任务;
_resolve () {
/*
将当前promise的状态由pending变为resolve状态,并执行成功后的注册函数
如果当前状态改变过了,则return
*/
// setTimeout(() => {
// }, 0);
window.addEventListener('message', () => {
if (this.status !== DIYPromise.PENDING) return
this.status = DIYPromise.RESOLVE;
let handler;
while (handler = this.resolvedQueues.shift()) {
handler();
}
});
window.postMessage('');
}
_reject () {
/*
将当前promise的状态由pending变为reject状态,并执行失败后的注册函数
如果当前状态改变过了,则return
*/
// setTimeout(() => {
// }, 0);
window.addEventListener('message', () => {
if (this.status !== DIYPromise.PENDING) return
this.status = DIYPromise.REJECT;
let handler;
while (handler = this.rejectedQueues.shift()) {
handler();
}
});
window.postMessage('');
}
这样就解决了上面的两个问题,但是还会一个问题,那就是当某处触发了这个message这个事件,则这个方法会被触发执行,所以可以带一下标识符window.postMessage('')做一些判断;
接下来要看一下回调传递的数据怎么处理?以及promise是如何实现.then()的链式调用的呢?
关于then的链式调用,那就涉及到promise中下面的问题:
let p1 = new Promise((res, rej) => {
res();
});
// 方式一:
p1.then(res => {
console.log('res1')
}).then(res => {
console.log('res2')
});
// 方式二
p1.then(res => {
console.log('res1')
});
p1.then(res => {
console.log('res2')
});
// 方式1和方式2有区别吗?
方式一和方式二显然是有很大区别的:
区别可以类比下面的:下面两者的不同之处
let arr = [1,2,3,4,5,6,7,8,9,0];
arr.slice(1,2).slice(1,2);
arr.slice(1,2);
arr.slice(1,2);
方式一总的两个then(),相当于都是注册给了p1对象,
方式二后面的then()相当于注册给了另外一个promise对象;这里的跟jq中的不一样,jq返回是是同一个对象
知道了then()的这种区别之后,那么DIYPromise对象如何实现这种写法呢?
所以要让then()返回一个DIYPromise对象:
then (resolvedHandler, rejectedHandler) {
// 这里传进来的函数不能直接调用,而应该类似推入事件队列里面去
// 理解事件原理
// this.resolvedQueues.push(resolvedHandler);
// this.rejectedQueues.push(rejectedHandler);
return new DIYPromise((resolve, reject) => {
function newResolvedHandler () {
resolvedHandler();
resolve();
}
function newRejectedHandler(params) {
rejectedHandler();
reject();
}
this.resolvedQueues.push(newResolvedHandler);
this.rejectedQueues.push(newRejectedHandler);
});
}
在调用resolve和reject方法的时候,还可以传入一些值,在后续调用then()方法中可以通过对应的函数接收到这个值;
需要注意的是:
如果在一个promise对象的状态改变后调用then(),则会立即执行所添加的对应的函数,所以必须根据当前promise对象的状态来做不同的处理。
如果是pending状态,则添加到队列中去,如果是resolved/reject状态则是立即执行。
上面代码仍然有巨大的问题,原生promise中的then如果有return值的话,后面的then中是可以拿到这个return值,但是目前DIYPromise对象没有实现这个,所以需要将这个return值保存起来,并将结果传递给下一个resolverHandler
then (resolvedHandler, rejectedHandler) {
// 这里传进来的函数不能直接调用,而应该类似推入事件队列里面去
// 理解事件原理
// this.resolvedQueues.push(resolvedHandler);
// this.rejectedQueues.push(rejectedHandler);
return new DIYPromise((resolve, reject) => {
function newResolvedHandler (params) {
let result = resolvedHandler(params);
resolve(result);
}
function newRejectedHandler(params) {
let result = rejectedHandler(params);
reject(result);
}
this.resolvedQueues.push(newResolvedHandler);
this.rejectedQueues.push(newRejectedHandler);
});
}
但是上面的方法仅仅是return一个值的情况,如果return的是一个promise对象呢?
let p1 = new DIYPromise((resolve) => {
resolve('终身成长');
});
let p2 = p1.then(res => {
console.log('then1', res);
return new DIYPromise((resolve) => {
setTimeout(() => {
console.log('settimeout');
resolve('返回的是对象');
}, 1000);
});
});
p2.then(res => {
console.log('then2', res);
});
这个时候就应该做一点兼容处理:比如
then (resolvedHandler, rejectedHandler) {
// 这里传进来的函数不能直接调用,而应该类似推入事件队列里面去
// 理解事件原理
// this.resolvedQueues.push(resolvedHandler);
// this.rejectedQueues.push(rejectedHandler);
return new DIYPromise((resolve, reject) => {
function newResolvedHandler (params) {
let result = resolvedHandler(params);
if (result instanceof DIYPromise) {
result.then(resolve, reject);
}else {
resolve(result);
}
}
function newRejectedHandler(params) {
let result = rejectedHandler(params);
reject(result);
}
this.resolvedQueues.push(newResolvedHandler);
this.rejectedQueues.push(newRejectedHandler);
});
}
完善一下失败的调用:
class DIYPromise {
static PENDING = "PENDING";
static RESOLVE = "RESOLVE";
static REJECT = "REJECT";
constructor ( handler ) {
if (typeof handler !== 'function') {
throw new TypeError('Promise resolver undefined is not a function');
}
this.status = DIYPromise.PENDING;
this.resolvedQueues = [];
this.rejectedQueues = [];
this.value;
handler(this._resolve.bind(this), this._reject.bind(this));
}
then (resolvedHandler, rejectedHandler) {
// 这里传进来的函数不能直接调用,而应该类似推入事件队列里面去
// 理解事件原理
// this.resolvedQueues.push(resolvedHandler);
// this.rejectedQueues.push(rejectedHandler);
return new DIYPromise((resolve, reject) => {
function newResolvedHandler (params) {
let result = resolvedHandler(params);
if (result instanceof DIYPromise) {
result.then(resolve, reject);
}else {
resolve(result);
}
}
function newRejectedHandler(params) {
let result = rejectedHandler(params);
if (result instanceof DIYPromise) {
result.then(resolve, reject);
}else {
reject(result);
}
}
this.resolvedQueues.push(newResolvedHandler);
this.rejectedQueues.push(newRejectedHandler);
});
}
}
写个demo测试一下:
let p1 = new DIYPromise((resolve) => {
resolve('终身成长');
});
let p2 = p1.then(res => {
console.log('then1', res);
return new DIYPromise((resolve,reject) => {
setTimeout(() => {
console.log('settimeout');
reject('返回的是对象');
}, 1000);
});
});
p2.then(res => {
console.log('then2', res);
}, rej => {
console.log('出问题了', rej);
});
这个时候,控制台是有打印出对应的信息。不过在原生的promoise中,是存在用catch()方法来捕获错误信息的,catch只接受一个函数,就是错误处理函数,那么catch方法该如何实现呢?
其实很简单,catch就是调用then方法,只是resolvedHandler是undefined,那么就需要处理一下newResolvedHandler和newRejectedHandler方法做容错兼容处理
catch(rejectedHandler) {
return this.then(undefined, rejectedHandler)
}
function newResolvedHandler (params) {
if (typeof resolvedHandler === 'function') {
let result = resolvedHandler(params);
if (result instanceof DIYPromise) {
result.then(resolve, reject);
}else {
resolve(result);
}
}else {
resolve(params)
}
}
function newRejectedHandler(params) {
if (typeof rejectedHandler === 'function') {
let result = rejectedHandler(params);
if (result instanceof DIYPromise) {
result.then(resolve, reject);
}else {
reject(result);
}
}else {
reject(params);
}
}
再来一个问题:promise中的finally()函数的实现又是怎么样的呢?
前面的then是正确的时候执行,catch是失败的时候执行,那么finally呢?它是正确或者失败都会执行的一个方法,所以需要添加一个数组来管理finally的函数队列:
this.finallyQueues = [];
finally最终调用的方法是_finally方法,跟resolve和reject方法类似需要接受传递过来的结构参数:
_finally(val) {
window.addEventListener('message', () => {
this.status = DIYPromise.RESOLVE;
this.value = val;
let handler;
while (handler = this.finallyQueues.shift()) {
handler(this.value);
}
});
window.postMessage('');
}
因为不管是成功还是失败都会调用finally方法,所以需要在resolve和reject方法总调用_finally()方法,并将对应的结果参数传递进函数,至此finally方法就可以正常使用了:
new DIYPromise((resolve, reject) => {
// resolve('终身成长');
reject('错误');
}).then(res => {
console.log('then2', res);
return '124' -->如果没有return值,那么finally接受到的参数将会是undefined
}).finally((val) => {
console.log('finally', val);
});
接下来将实现静态方法resolve,在原生的promise中,可以直接调用resolve方法,其实使用的就是静态方法resolve
static resolve(val) {
return new DIYPromise((resolve) => {
resolve(val);
});
}
static reject(val) {
return new DIYPromise((resolve, reject) => {
reject(val);
});
}