✅作者简介:大家好我是无处不楼台,是一个什么都会一点的大前端小白博主!
个人主页:无处不楼台的博客_CSDN博客-JavaScript学习,项目开发,node学习领域博主
系列专栏:知识体系梳理
如果觉得博主的文章还不错的话,请三连支持一下博主哦
目录
前言
一、异步编程发展史
1、回调函数时代
2、Promise时代
3、co+Generator
4、async+await
二、回调地域的终结
1)、回调函数延迟绑定
2)、返回值穿透
3)、错误冒泡
三、微任务的引入
四、链式调用的实现
五、resolve、reject以及finally
1、实现Promise.resolve:
2、实现Promise.reject
3、实现Promise.prototype.finally
六、实现Promise的all和race
1、实现Promise.all
2、实现promise.race:
结束语
要深入了解promise,我们首先要明白为什么会出现promise,以及promise在js异步编程中的地位;
在最初期,为了解决js异步问题,我们在代码中大量使用回调函数,期待在一个函数执行后再触发另一个回调函数;
但是如果在回调当中嵌套大量其他回调,就会出现回调地域;
因为回调地域嵌套的回调层级太多,这种代码的可读性和可维护性都是非常差,
fs.readFile('1.json', (err, data) => {
fs.readFile('2.json', (err, data) => {
fs.readFile('3.json', (err, data) => {
fs.readFile('4.json', (err, data) => {
});
});
});
});
而且如果任务失败,需要对里面的每一个任务的失败情况进行处理,增加了代码的混乱程度;
ES6中的promise就很好的解决了回调地域的问题,同时合并了错误的处理;
readFilePromise('1.json').then(data => {
return readFilePromise('2.json')
}).then(data => {
return readFilePromise('3.json')
}).then(data => {
return readFilePromise('4.json')
});
ES6同时也提供了generator解决问题,generator利用协程控制代码的执行顺序,co库让代码顺序执行;
co(function* () {
const r1 = yield readFilePromise('1.json');
const r2 = yield readFilePromise('2.json');
const r3 = yield readFilePromise('3.json');
const r4 = yield readFilePromise('4.json');
})
async+await是ES7新增的关键字,凡是加上async的函数都默认返回一个promise对象,而更重要的是async+await也能让异步代码以同步的方式来书写,而不需要第三方库的支持;
Promise利用了三大技术手段来解决回调地域:
在promise中,回调函数不是直接声明的,而是通过后面的then方法传入的,即延迟传入;
我们会根据then中回调函数的传入值来创建不同类型的promise,然后把返回的promise穿透到外层,以供后续的调用;
回调函数延迟绑定和返回值穿透结合产生了链式调用的效果;
promise会使用catch捕捉错误,前面promise产生了错误会一直冒泡到最后面被catch捕捉
promise中的执行函数都是同步进行的,但是里面存在着异步操作,在异步操作结束后会调用resolved方法,或者中途遇到错误调用reject方法,这两者都是作为微任务进入到事件循环中的,那为什么promise要引入微任务的方式来进行回调操作呢?
这其实就是如何处理回调的问题;
1)、使用同步回调,直到异步任务进行完,再进行后面的任务
2)、使用异步回调,将回调函数放在宏任务队列的队尾
3)、使用异步回调,将回调函数放到当前宏任务中的最后面。
第一种方式显然不可取,因为同步的问题非常明显,会让整个脚本阻塞住,当前任务等待,后面的任务都得不到执行,而这部分等待的时间是可以拿来完成其他事情的,导致CPU的利用率非常低,而且无法实现延迟绑定;
如果采用第二种方式,那么执行回调(resolved/reject)的时机应该是在前面的所有宏任务完成之后,倘若现在的任务队列很长,那么回调迟迟得不到执行,会造成应用卡顿;
为了解决问题,也考虑promise延迟绑定的需求,promise1采用第三种方式,即引入微任务,即把resolve(reject)回调的执行放在宏任务的末尾;
这样微任务就解决了两大痛点:
1)、采用异步回调替代同步回调解决了浪费CPU性能的问题;
2)、放到当前宏任务最后执行,解决了回调执行的实时性问题;
前面说过,Promise的返回值穿透和回调函数延迟绑定两个特性实现了链式调用,这里我们具体说说:
第一版代码:(不是最终版本,后面会讨论如何改进)
const PENDING="pending";
const FULFILLED="fulfilled";
const REJECTED="rejected";
function MyPromise(executor){
let self=this;//取出promise实例
self.value=null;//定义vlaue值
self.error=null;//
self.status=PENDING;
self.onFulfilled=null;//成功的回调函数
self.onRejected=null;//失败的回调函数
const resolve=(value)=>{
if(self.status!==PENDING)return;
setTimeout(()=>{
self.status=FULFILLED;
self.value=value;
self.onFilfilled(self.value);
})
}
const reject=(error)=>{
if(self.status!==PENDING)return;
setTimeout(()=>{
self.status=REJECTED;
self.error=error;
self.onRejected(self.error);
})
}
executor(resolve,reject);
}
MyPromise.prototype.then=function(onFilfilled,onRejected){
if(this.status===PENDING){
this.onFulfilled=onFulfilled;
this.onRejected=onRejected;
}else if(this.status==FULFILLED){
onFulfilled(this.value);
}else{
onRejected(this.error);
}
return this;
}
我们都知道,promise的本质是一个有限状态机,存在三种状态:
1)、pending
2)、fulfilled
3)、rejected
对于promise而言,状态的改变是不可逆的,即由等待态变为其他的状态后,就无法再次改变了;
现在我们对于上面那一版promise进行一些优化:
1)、在第一版的promise中,如果我们对于一个promise实例延迟绑定多个回调函数,那么也只能执行一个;
所以我们做出以下修改:
self.onFulfilledCallbacks=[];
self.onRejectedCallbacks=[];
MyPromise.prototype.then=function(onFulfilled,onRejected){
if(this.status===PENDING){
this.onFulfilledCakkbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}else if(this.status==FULFILLED){
onFulfilled(this.value);
}else{
onRejected(this.value);
}
return this;
}
2)、由于then中我们返回的是this,这会导致在链式调用时,每一个返回的都是第一个promise实例,修改如下:
MyPromise.prototype.them=function(onFulfilled,onRejected){
let bridgePromise;
let self=this;
if(self.status===PENDING){
return bridgePromise=new Mypromise((resolve,reject)=>{
self.onFulfilledCallbacks.push((value)=>{
try{
let x=onFulfilled(value);
resolved(x);
}catch(e){
reject(e);
}
});
self.onRejectedCallbacks.push((error)=>{
try{
let x=onRejected(error);
resolved(x);
}catch(e){
reject(e);
}
})
})
}
}
除此之外,还需要对返回promise的情况进行处理:
funciton resolvePromise(bridgePromise,x,resolve,reject){
if(x instanceof MyPromise){
if(x.status==PENDING){
x.then(y=>{
resolvePromise(bridgePromise,y,resolved,reject);
},error=>{
reject(error);
})
}else{
x.then(resolved,reject);
}
}else{
resolve(x);
}
}
实现resolve静态方法有三个要点:
1)、如果传参为promise,直接返回他;
2)、如果传参为一个thenable对象,返回的promise对象会跟随着这个对象,采用它的最终状态作为子级的状态
3)、其他情况下,直接返回以该值为成功状态的promise对象
Promise.resolved=(param)=>{
if(param instanceof Promise) return param;
return new Promise((resolved,reject)=>{
if(param&¶m.then&¶m.then==='function){
param.then(resolve,reject);
}else{
resolve(param);
}
})
}
Promise.reject中传入的参数会作为一个reason原封不动地往下传:
Promise.reject=function(reason){
return new Promise((resolve,reject)=>{
reject(reason);
});
}
无论当前promise是成功还是失败,调用finally之后都会执行finally中传入的函数,并将值原封不动的往下传;
Promise.prototype.finally=function(callback){
this.then(value=>{
return Promise.resolve(callback()).then(()=>{
return value;
})
},error=>{
return Promise.resolve(callback()).then(()=>{
throw error;
})
})
}
对于all方法而言,
1)、如果传入参数为一个空的可迭代对象,则直接进行resolve
2)、如果参数中有一个promise失败,那么promise.all返回的promise对象失败
3)、任何情况下,promise.all返回的promise的完成状态的结果都是一个数组
Promise.all=function(promises){
return new Promise((resolve,reject)=>{
let result=[];
let index=0;
let len=promises.length;
if(len===0){
resolve(result);
return;
}
for(let i=0;i{
result[i]=data;
index++;
if(index==len) resolve(result);
}).catch(err=>{
reject(err);
})
}
})
}
Promsie.race=function(promises){
return new Promise((resolve,reject)=>{
let len=promises.length;
if(len==0)return;
for(let i=0;i{
resolve(data);
return;
}).catch(err=>{
reject(err);
return;
})
}
})
}
明天写diff算法!!!