假设我们需要发三次请求A,B,C,B请求需要用到A请求回来的数据,C需要用到B请求回来的数据,怎么发送?
$.ajax({
url:'jsonA.json',
success(data) {
$.ajax({
url:'jsonB.json',
success(data) {
$.ajax({
url:'jsonC.json',
success(data) {
}
})
}
})
}
})
这样的代码就是所谓的回调地狱(回调金字塔)了,如果还有DEFG等等请求呢,这么嵌套下去的代码可读性复写性等都太差
那么,怎么解决呢?
1.基于发布订阅来解决 重点不在发布订阅上,就不上代码,讲讲思路: B函数订阅A函数,C函数订阅B函数,A函数执行成功时发布给B,B函数执行成功时发布给C 2.使用Promise来解决回调地狱(回调金字塔)
promise是用来管理异步的一个内置类,其有三种状态
pedding:就绪状态
resolved(fulfilled):成功状态
rejected:失败状态
以上三张图都是说promise的执行机制原理,但是事实上我们在项目都不会这么用,而是我们手动给其then以及catch返回一个带状态的promise的实例。
那么,还是上面那个需求:
假设我们需要发三次请求A,B,C,B请求需要用到A请求回来的数据,C需要用到B请求回来的数据,怎么发送?
使用promise来实现一下看看:
// 先将A,B,C封装成三个方法,手动给方法返回promise对象,
let queryA = () => {
return new Promise((resolve) => {
$.ajax({
url:'jsonA.json',
success(data) {
resolve(data);
}
})
}
)
};
let queryB = (result) => {
return new Promise((resolve) => {
$.ajax({
url:'jsonB.json',
success(data) {
resolve(data);
}
})
}
)
}
let queryC = (result) => {
return new Promise((resolve) => {
$.ajax({
url:'jsonC.json',
success(data) {
console.log(data);
}
})
}
)
}
let p = queryA();
p.then(queryB).then(queryC);
这时候就会有小伙伴疑惑了,咦,这个promise的实现跟上面原理中的promise实现完全不同哎,小编你确定你没在逗我?
那么,再看看这张图呢
那么现在明白了promise的一些运行的原理了,那么如果我们的B请求不依赖于A请求,C请求依赖于A和B请求呢?
promise的原型上为我们提供了一个all方法(状态全部为fulfilled时才改变实例的状态为fulfilled)
let p=Promise.all([queryA(),queryB()]);
p.then(queryC);
queryA和queryB全部执行完毕且返回的两个promise实力的状态都是fulfilled的时候p的状态才变为fulfilled, 如果有一个状态是rejected那么p的状态就是rejected,queryA和queryB返回值会合并成一个数组传给queryC
promise的原理就到这了,那么既然懂了其原理,我们能不能自己写一个promise出来?
先请大家认真看一张图便于理解后面重写的promise代码
如果你认真看了上面的图,我相信你能理解下面的代码
class MyPromise { // excutor执行器,就是我们new的时候传进来的函数 constructor(excutor){ // 实例状态 this.status = 'pending'; // 成功状态事件池 this.fulfilledCallbacks = []; // 失败状态事件池 this.rejectedCallbacks = []; // 记录执行resolve和reject时的参数 this.value = undefined; let resolve = (result) => { // 如果不是pending那么状态就已经凝固,不能更改 if(this.status === 'pending'){ this.status = 'resolved'; this.value = result; // 为了避免还没有执行then,成功状态事件池还没添加这里就同步执行了,因为foreach是一个同步方法 // 所以需要把循环事件池处理成异步的,考虑到then也是异步的,所以不仅要将这里处理成异步的,还要 // 要求这里的异步在then的异步后面执行,then是微任务,加个定时器让其变成宏任务,就一定时在then // 后面被执行 let time = setTimeout(() => { clearTimeout(time); // 循环执行成功状态事件池中的事件 this.fulfilledCallbacks.forEach(item => item(this.value)) },0) } }; let reject = (reason) => { if(this.status === 'pending'){ this.status = 'rejected'; this.value = reason; let time = setTimeout(() => { clearTimeout(time); this.rejectedCallbacks.forEach(item => item(this.value)) },0) } }; try { excutor(resolve,reject); }catch (e) { reject(e); } } // then方法就是重点了,传入两个回调函数,但是并不立即执行,而是等this的status状态发生变化 // 时在其resolve或者reject方法中执行 then(onFulfilled,onRejected){ // 需要链式调用then方法,所以then方法的返回值必须是一个promise的实例,而resolve和reject // 两个形参则是用来改变返回的这个promise的状态的,因为下一个then中的回调还挂着返回这个promise // 对象的身上,需要通过控制resolve和reject的执行来控制下一个then中执行的方法 return new MyPromise((resolve,reject) => { // 向成功状态事件池添加回调,只添加并没有执行,this指向上一个promise实例, // 什么时候执行?在调用this的resolve方法时被执行,result就是传进来的this.value this.fulfilledCallbacks.push((result) => { // 如果then中的方法执行时报错,直接执行返回的promise的reject方法触发下一个then的失败回调执行 try { // onFulfilled是什么?是then中的第一个方法,result是上一个promise对象传过来的参数 // onFulfilled(result)执行的返回值如果是一个普通值,就通知下一个then中的成功回调执行 // 这个普通值作为参数传进去 let x = onFulfilled(result); x instanceof MyPromise // 如果返回的是一个promise对象,那就需要用返回的promise对象的状态来控制本次返回的promise // 对象的状态以达到控制下一个then中哪个回调的执行,这里很妙的用了then方法,x是then中回调返 // 回的promise对象,resolve和reject两个实参则是控制当前返回peomise对象状态的方法,这样如 // 果then回调返回的promise状态时resolve就会执行当前返回promise对象的resolve以触发下 // 一个then中的成功回调的执行,如果then中回调的状态为reject就会执行传进去的reject也就 // 是控制当前返回promise对象状态的reject,就可以触发下一个then中的失败回调的执行 ? x.then(resolve,reject) : resolve(x); }catch (e) { reject(e); } }) // 向失败状态事件池添加回调,原理与成功事件池添加一样 this.rejectedCallbacks.push((reason) => { try { let x = onRejected(reason); x instanceof MyPromise ? x.then(resolve,reject) : resolve(x) }catch (e) { reject(e); } }) }) } } // 测试代码 new MyPromise((resolve,reject) => { console.log(1); reject(); }).then((res) => { console.log(2); },(err) => { console.log(3); }).then((res) => { console.log(4); },(err) => { console.log(5); });
每一步都写有注释,细节不懂的可以评论