Promise
- 其实就是一个类,内部保存着回调队列,通过暴露resolve和reject方法触发对应回调
- 内部有三种状态值,pending,fulfiled,failed
- Promise是微任务,与settimout等宏任务有差别,执行顺序时是,先主线程,然后微任务,然后宏任务
- 有以下几种方法:then, catch, all, race, resolve, reject
- 一旦实例了一个Promise,其就一定会执行,无法停止
- Promise中,由于报错也会触发reject,所以不会中断程序
- catch其实是then的语法糖,相当于then(null,rej),即成功回调传了空
- 如果then中,没有传入回调,则promise的resolve或reject值会传递到下一个then或catch中,如下:
const p = new Promise((res) => res(2));
p.then(null).then((data) => console.log(data)); // 2
p.then(() => {}).then((data) => console.log(data)); // undefined
p.catch((data) => {console.log('catch', data)}).then((data) => console.log('then', data)); // then, 2
实现
内部变量
function myPromise(func) {
this.status = 'pending'; // 状态
this.value = undefined; // 值
this.resolveList = []; // 成功回调
this.rejectList = []; // 失败回调
}
内部resolve和reject方法
- 同时执行传入的func
function myPromise(func) {
this.status = 'pending'; // 状态
this.value = undefined; // 值
this.resolveList = []; // 成功回调
this.rejectList = []; // 失败回调
const resolve = (data) => {
if (this.status === 'pending') {
this.status = 'fulfiled';
this.value = data;
this.resolveList.forEach((resFunc) => resFunc(data));
this.resolveList.length = 0;
}
};
const reject = (data) => {
if (this.status === 'pending') {
this.status = 'failed';
this.value = data;
this.rejectList.forEach((resFunc) => resFunc(data));
this.rejectList.length = 0;
}
};
if (typeof func === 'function') {
try {
func(resolve, reject); // promise传入的func是马上执行的,但回调resolve和reject是异步微任务
} catch(err) {
reject(err);
}
}
}
then方法
- then方法, 定义在原型链上, 返回一个promise
- 分三种状态处理,pending时仅把回调添加到回调队列,同时promise化,fulfilled或failed时直接执行,并返回promise
function handleResolve(res, rej, resCb, rejCb) {
queueMicrotask(() => {
// 如果没有定义回调,将值传递给下一个
try {
const ret = typeof resCb === 'function' ? resCb(this.value) : this.value;
if (ret instanceof myPromise) {
ret.then(res, rej);
} else {
res(ret);
}
} catch (err) {
rej(err);
}
});
}
function handleReject(res, rej, resCb, rejCb) {
queueMicrotask(() => {
// 如果没有定义回调,将值传递给下一个
try {
const ret = typeof rejCb === 'function' ? rejCb(this.value) : this.value;
if (ret instanceof myPromise) {
ret.then(res, rej);
} else {
rej(ret);
}
} catch (err) {
rej(err);
}
});
}
myPromise.prototype.then = function (resCb, rejCb) {
if (this.status === 'pending') {
return new myPromise((res, rej) => {
this.resolveList.push(() => {
handleResolve(res, rej, resCb, rejCb));
});
this.rejectList.push(() => {
handleReject(res, rej, resCb, rejCb));
});
});
} else if (this.status === 'fulfiled') {
return new myPromise((res, rej) => {
handleResolve(res, rej, resCb, rejCb));
});
} else if (this.status === 'failed') {
return new myPromise((res, rej) => {
handleReject(res, rej, resCb, rejCb));
});
}
};
- 这里看到如果resCb或rejCb传入为空,会将promise的value传递给返回的promise
- 并且如果cb返回的是promise,会以那个promise的res或rej触发上层的res或rej,同时cb中的data值是内层promise的value
- 例子如下:
const p1 = new Promise((res) => res(1));
p1.then(null).then((data) => console.log(data));
p1.then(() => {}).then((data) => console.log(data));
const p2 = new Promise((res, rej) => rej(2));
p2.then(() => {}).catch((err) => console.log(err));
p2.then(() => {}, () => {}).catch((err) => console.log(err));
/*
输出如下:
1
undefined
2
undefined
*/
- 即如果promise的resolve或reject没有在then中传入对应的回调函数,会将其value不断的传递下去
微任务创建时机
- 从reslove以及reject定义可以看到,其仅仅只是改变promise的状态以及其value,那么微任务,即异步是在哪创建的呢?注意下面的例子有:
const p = new Promise((res) => res(1));
p.then(() => console.log('p1 then'))
.then(() => console.log('p1 then then'));
const p2 = new Promise((res) => res(2));
p2.then(() => console.log('p2 then'));
/*
输出如下:p1 then -> p2 then -> p1 then then
*/
- 微任务在then中创建,回到then方法的定义中,可以看到handleResolve以及handleReject,利用了queueMicrotask创建微任务
结合上诉例子以及then方法,可以看到
- 当p1.then注册了一个回调函数并把他放在微任务后,返回了一个promise
- 但该返回的promise中的resolve是放在微任务中的,即当前不执行then返回promise的reslove
- 所以在返回的promise中用then注册的回调函数,是放到resolveList中的,也就是当前不执行
- 然后到p2.then,其也产生了一个微任务
- 当前微任务队列有两个任务,先执行p1 then的输出,然后执行p1.then返回promise的resolve,又新建了一个微任务,放到微任务队列
- 然后执行p2 then,p1 then then
catch方法
- catch方法,即then的语法糖
myPromise.prototype.catch = (rej) => myPromise.prototype.then(null, rej);
all方法
- all方法,传入一个数组,输出一个promise,全部resolve时,输出promise状态变为fulfiled,触发resolve
- 当任何一个传入的promise失败时,输出promise状态变failed,触发reject,但并不会终止其他promise继续执行,只是其他promise的执行已经不会影响promise.all返回promise的状态了
myPromise.prototype.all = (iterable) => {
const len = iterable.length;
const result = [];
return new myPromise((resolve, reject) => {
let index = 0;
iterable.forEach((item) => {
item.then((data) => {
result.push(data);
index++;
if (index === len) resolve(result);
}, (err) => {
reject(err);
});
});
});
}
race方法
- race方法,返回promise数组中第一个执行完毕的状态,无论是fulfiled还是failed,但不影响数组中其余promise继续执行
myPromise.prototype.race = (iterable) => {
let done = false;
return new myPromise((resolve, reject) => {
iterable.forEach((item) => {
item.then((data) => {
if (!done) {
done = true;
resolve(data);
}
}, (err) => {
if (!done) {
done = true;
reject(err);
}
});
});
});
}
- 其中上面的done标志可以去掉,因为promise中,resolve两次,只会在首次执行then函数,具体原因是,第一次resolve时,执行resolveList中的回调函数,执行完毕后会把其清零,即如果没有在执行完毕后再注册新的then方法,第二次resolve不会触发回调
- 另外即使在第二次reslove后新注册了一个then,其获取的值也是第一次resolve的值,而不是第二次resolve的值,这是因为更新promise内的value只会发生在promise状态从pending变为fulfiled或failed的时候,而第一次resolve已经让状态变了,不会再触发value的更新,即promise的状态会凝固,例子如下:
const p1 = new Promise((res) => {
setTimeout(() => { console.log('res(1)'); res(1)}, 1000);
setTimeout(() => { console.log('res(2)'); res(2)}, 3000);
});
p1.then((data) => console.log(data));
setTimeout(() => {
console.log('then again');
p1.then((data) => console.log(data));
}, 5000);
/*
输出 res(1) -> 1 -> res(2) -> then again -> 1
最后输出1而不是2
*/
- 回到race,上述race方法可简化写成如下
myPromise.prototype.race = (iterable) => {
return new myPromise((resolve, reject) => {
iterable.forEach((item) => {
item.then(resolve, reject);
});
});
}
外部resolve和reject方法
- reslove和reject方法,其实就是暴露内部定义的reslove和reject,返回一个promise
myPromise.prototype.reslove = (data) => {
return new myPromise((resolve, reject) => {
resolve(data);
});
};
myPromise.prototype.reject = (data) => {
return new myPromise((resolve, reject) => {
reject(data);
});
};