众所周知,对于存在异步编程而产生的回调地狱问题,Promise的出现很好的解决了这样的问题。
首先,我们所要先了解的是,回调地狱是什么?
那......
什么是回调地狱?
某些异步任务想要实现按顺序执行的话,需要将回调函数层层嵌套堆叠,但是这种层层嵌套的回调函数经常会导致回调地狱问题。比如:
// 吃饭
function Eat(callback) {
setTimeout(() => {
callback("eat");
}, 1000);
}
// 煮饭
funtion Cook(callback) {
setTimeout(() => {
callback("cook");
}, 1000);
}
// 清洗厨具
function Clean(callback) {
setTimeout(() => {
callback("clean");
}, 1000);
}
// 完成吃饭
Eat((data) => {
console.log(data);
Cook((data) => {
console.log(data);
Clean(() => {
console.log(data);
})
})
})
例如以上代码中所展示的,要完成A任务,必须要相继完成多个(B、C、D...)任务,而这样实现的话,调用的方法越多,就会使得代码维护愈发困难,这种可复用性差、迭代性差的回调函数层层嵌套方式,就是所谓的回调地狱。
而Promise解决回调地狱问题,并不是控制函数执行的顺序(这个是控制不了的),而是通过控制异步代码处理的顺序来完成的。
Promise中的resolve和reject两个回调方法就是用来实现异步任务同步执行的。
Promise的基础认知
Promise的三种状态
Promise有三种状态,分别是:pending(进行中)、fullfilled(成功)、rejected(失败)。
resolve处理之后的结果返回的是fullfilled,reject处理之后的结果返回的是rejected,而如果没有处理Promise的返回结果则会一直在pending进行中。
let p = new Promise((resolve,reject) => {
resolve("fullfilled"); // 成功 fullfilled
reject("rejected"); // 失败 rejected
});
Promise.then
Promise通过.then完成下一步,会返回resolve中传递的结果(成功和失败),在.then中再新建一个Promise对象的话,便可以继续执行下一步,返回下一个结果。
而.then在什么情况下才能执行呢?
只有在Promise中成功调用了resolve方法或者是return数据之后,才会执行.then方法。
Promise.catch
.catch是在reject返回错误结果之后才会执行。
Promise其他方法
Promise还存在其他方法:
1、 原型方法: finally (包括catch)
2、 静态方法: race 、 all 、 allSettled 、 resolve 、 reject...
如何用JS实现简单版的Promise呢?
首先,Promise是一个可以创建的实例对象。那我们可以通过class类虚拟建一个Promise对象。
与此同时,Promise对象在创建之时,还有两个回调函数resolve 和 reject。 Promise中的回调会传递结果到下一步,则需要将其结果赋值给Promise中的“结果”(PromiseResult)。还需要根据回调函数改变其状态(PromiseState)
class Promise {
constructor(handle) {
this['[[PromiseState]]'] = 'pending';
this['[[PromiseResult]]'] = undefined;
handle(this.#resolve.bind(this), this.#reject.bind(this));
}
#resolve(val) {
this['[[PromiseState]]'] = 'fulfilled';
this['[[PromiseResult]]'] = val;
}
#reject(err) {
this['[[PromiseState]]'] = 'rejected';
this['[[PromiseResult]]'] = err;
}
}
then怎么实现呢?
.then会调用一个回调函数,该回调函数会返回resolve/reject中传递的结果(前提是:调用了resolve方法),我们可以设置一个全局的resolveFn和rejectFn用来作为判断then中执行了哪些回调,并可以在resolve/reject中调用将数据传递出去。
class Promise {
constructor(handle) {
this['[[PromiseState]]'] = 'pending';
this['[[PromiseResult]]'] = undefined;
this.resolveFn = undefined;
this.rejectFn = undefined;
handle(this.#resolve.bind(this), this.#reject.bind(this));
}
#resolve(val) {
this['[[PromiseState]]'] = 'fulfilled';
this['[[PromiseResult]]'] = val;
this.resolveFn && this.resolveFn(val);
}
#reject(err) {
this['[[PromiseState]]'] = 'rejected';
this['[[PromiseResult]]'] = err;
this.rejectFn && this.rejectFn(err);
}
then(onResolved, onRejected) {
this.resolveFn = onResolved;
this.rejectFn = onRejected;
}
}
然而这样实现的话会产生很多问题,咱就是说,多个then的情况的话,不可以保证,且不能正确的执行事件循环的顺序。
解决多个then、链式操作和事件循环问题
多个then的话,我们可以通过定义一个数组(resolveQueue 和 rejectQueue)用来存储多个then中回调执行的函数,当第一个回调执行完成之后,再执行数组中下一个回调(可以通过shift将首项取出来)。
事件循环可以通过MutationObserver对象模拟执行微任务。
链式操作,我们可以通过在then中返回一个新建的Promise对象,这样的话,可以不断的对其return的Promise对象进行.then。
如下:
class Promise {
constructor(handle) {
this['[[PromiseState]]'] = 'pending';
this['[[PromiseResult]]'] = undefined;
// this.resolveFn = undefined;
// this.rejectFn = undefined;
this.resolveQueue = [];
this.rejectQueue = [];
handle(this.#resolve.bind(this), this.#reject.bind(this));
}
#resolve(val) {
this['[[PromiseState]]'] = 'fulfilled';
this['[[PromiseResult]]'] = val;
// this.resolveFn && this.resolveFn(val);
const run = () => {
// 解决多个then的问题
let cb;
while(cb = this.resolveQueue.shift()) {
// 将第一项删除出来赋值给cb
cb && cb(val);
}
}
// run(); // 不能异步
// setTimeout(run); // 影响微任务宏任务运行
// 宏任务微任务处理
const observer = new MutationObserver(run);
observer.observe(document.body, {
attributes: true,
});
document.body.setAttribute('test', 'value');
}
#reject(err) {
this['[[PromiseState]]'] = 'rejected';
this['[[PromiseResult]]'] = err;
// this.rejectFn && this.rejectFn(err);
const run = () => {
// 解决多个then的问题
let cb;
while(cb = this.rejectQueue.shift()) {
// 将第一项删除出来赋值给cb
cb && cb(err);
}
}
// run(); // 不能异步
// setTimeout(run); // 影响微任务宏任务运行
// 宏任务微任务处理
const observer = new MutationObserver(run);
observer.observe(document.body, {
attributes: true,
});
document.body.setAttribute('test', 'value');
}
then(onResolved, onRejected) {
// if(this['[[PromiseState]]'] === 'fulfilled') {
// onResolved && onResolved(this['[[PromiseResult]]']);
// } else {
// onRejected && onRejected(this['[[PromiseResult]]']);
// }
// 链式操作
return new Promise((resolve, reject) => {
let resolveFn = function(val) {
let result = onResolved && onResolved(val);
// 如果返还的是Promise对象 返还值处理
if(result instanceof CPromise) {
result.then(res => {
resolve(res);
})
} else {
resolve(result);
}
}
this.resolveQueue.push(resolveFn);
let rejectFn = function(err) {
onRejected && onRejected(err);
reject(err);
}
this.rejectQueue.push(rejectFn);
})
// this.resolveFn = onResolved;
// this.rejectFn = onRejected;
// this.resolveQueue.push(onResolved);
// this.rejectQueue.push(onRejected);
}
}
一些其他方法的简单实现
Promise中不仅有then,还有一些其他的方法,关于这些方法,其实都可以通过在其中新建一个Promise对象,然后通过已有方法来帮助实现。
catch(fn) {
return this.then(undefined, fn);
}
static resolve(val) {
return new Promise(resolve => {
resolve(val);
})
}
static reject(err) {
return new Promise(reject => {
reject(err);
})
}
static allSettled(lists) {
// 返回结果与lists的长度保持一致
let resArr = new Array(lists.length);
// 数目一致之后可以直接返回resArr
let num = 0;
// 返回一个新的Promise对象
return new Promise(resolve => {
// 循环遍历传入的数组
lists.forEach((item, key) => {
let obj = {};
// 处理传入的实例对象中的结果,将其结果保存到resArr中,然后再将resArr作为最终结果返回
item.then(res => {
obj["status"] = "fulfilled";
obj["value"] = res;
resArr[key] = obj;
num++;
if(num >= lists.length) {
resolve(resArr);
}
}, err => {
obj["status"] = "rejected";
obj["reson"] = err;
resArr[key] = obj;
num++;
if(num >= lists.length) {
resolve(resArr);
}
})
})
})
}
可以使用
async/await来实现Promise,是Promise的一种高级写法,更加的方便。