当前位置: 首页 > 后端技术 > Node.js

一杯开心茶手搓Promise

时间:2023-04-03 16:36:12 Node.js

作者:JowayYoung仓库:Github、CodePen博客:官网、掘金、师傅、知乎公众号:智商前端特别声明:原创不易,未经授权不得转载或抄袭不允许转载,如需转载请联系作者授权本文。本文由作者懒咖喱妹创作,收录于作者技术文章专栏。前言我们都知道JS是单线程的,只有前一个任务完成了才能执行下一个任务。显然,在浏览器上,这个执行会阻塞浏览器对DOM的渲染。所以在JS中会有很多异步操作,那么JS是如何实现异步操作的呢?有必要想到Promise对象。文中先理解Promise,然后写代码实现Promise。理解PromisePromise是JS解决异步编程的方法之一,它的英文意思是promise。在程序中可以理解为等待一段时间执行,等待一段时间在JS中是异步的。异步是指需要很长时间才能完成的任务,比如网络请求、读取文件等。Promise是一个实例对象,可以从中获取异步处理的结果。Promise有3种状态,分别是pending(进行中)、fulfilled(成功)、rejected(失败)。只有异步操作可以改变Promise的状态,没有其他操作可以改变它。而且状态改变之后,就不会再改变了。它只能从pending变为fulfilled或pending变为rejected。这也是Promise的一个显着特点。使用Promise正如上面提到的,Promise是一个对象,所以它必须由它的构造函数创建。它的构造函数接受一个函数作为参数,它的函数有两个参数,resolve和reject。resolve将状态从pending更改为fulfiled并在成功时调用。reject将状态从pending更改为rejected,并在失败时调用。functionRunPromise(num,time){returnnewPromise((resolve,reject)=>{console.log("开始执行");if(num%2===0){setTimeout(()=>{resolve(`偶数调用resolve,num为${num}`);},time);}else{setTimeout(()=>{reject(newError(`奇数调用rejected,num为${num}`));},time);}});}Promise对象上有then()和catch()方法。then()接收2个参数,第一个对应resolve回调,第二个对应reject回调。catch()和then()的第二个参数一样,都是用来接受reject回调的,但是它还有另外一个作用。如果在then()中执行resolve回调时抛出异常,则该异常可能是代码定义抛出的。也有可能是代码错误,这个异常会在catch()中被捕获。RunPromise(22,2000).then(res=>{console.log("then的第一个参数被执行");console.log(res);console.log(newres);},error=>{console.log("then'ssecondparameterisexecuted");console.log(error);}).catch(error=>{console.log("error");console.log(error);});//输出如下如下://开始执行//then的第一个参数执行//遇到偶数时调用resolve,此时num为22//error//ReferenceError:newresisnotdefined上面的例子中,RunPromise()调用resolve,then()的第一个参数对应回调,状态从pending变为fulfilled,状态不会再改变。在then()中,变量newres还没有被定义,所以程序出错了,它的异常被catch()捕获了。一般来说then()可以使用第一个参数,因为catch()和then()的第二个参数一样,同样可以捕获异常。PromisePromise的实现和使用方法已经大致了解了。为了理解Promise是如何实现的,我们手工实现了一个简单的Promise方法,简单的实现了then(),异步处理,链式调用。用最简单的思路,函数要实现什么功能,给相应的函数分配相应的实现代码即可。下面的代码都是用ES6写的。定义Promise构造函数创建一个Promise对象使用newPromise((resolve,reject)=>{}),可以知道Promise构造函数的参数是一个函数,我们定义为implement,函数有2个参数:resolve,reject,这两个参数都可以执行,所以也是一个函数。语句完成后,需要解析状态。上面说了Promise有3种状态,这里就不细说了,直接上传代码即可。//ES6声明构造函数类MyPromise{constructor(implement){this.status="pending";//初始化状态为pendingthis.res=null;//成功时的值this.error=null;//failureconstresolve=res=>{//resolve的作用是将状态从pending变为fulfilled,将success的值存入this.resif(this.status==="pending"){this.status="已完成";这个.res=res;}};constreject=error=>{//reject的作用只是将状态从pending变为rejected,并将失败的值存入this.errorif(this.status==="pending"){this.status=“拒绝”;this.error=错误;}};//当程序报错时,会执行reject,所以这里添加错误捕获,直接执行rejecttry{implement(resolve,reject);}抓住(错误){拒绝(错误);}}}then函数我们在使用Promise的时候都知道then()有2个参数,分别是状态fulfilled和rejected时的回调函数,我们这里定义2个函数为onFulfilled和onRejected。classMyPromise{constructor(implement){...}then(onFulfilled,onRejected){//当状态为fulfilled时,调用onFulfilled并传入成功值if(this.status==="fulfilled"){onFulfilled(这个.res);}//当状态为拒绝时,调用onRejected并传入失败值if(this.status==="rejected"){onRejected(this.error);}}}asynchronouslyprocessedto到这里已经实现了基本的代码,但是在异步的时候就会出现问题。比如本文开头以Promise为例,在setTimeout()中使用了resolve。这时在then()中,状态还是pending,所以没办法调用onFulfilled。所以我们先保存处理函数(onFulfilled或者onRejected),然后在调用then()的时候再使用这些处理函数。因为Promise可以定义多个then(),所以这些处理函数都存储在数组中。实现思路:then()增加状态为pending的判断,当此时存入处理函数resolve或reject时循环调用处理函数这个.res=null;这个.error=null;this.resolveCallbacks=[];//成功时回调的处理函数this.rejectCallbacks=[];//失败时回调的处理函数constresolve=res=>{if(this.status==="pending"){this.status="fulfilled";这个.res=res;this.resolveCallbacks.forEach(fn=>fn());//遍历成功处理函数}};constreject=error=>{if(this.status==="pending"){this.status="rejected";this.error=错误;this.rejectCallbacks.forEach(fn=>fn());//循环执行失败处理函数}};尝试{实施(解决,拒绝);}抓住(错误){拒绝(错误);}}then(onFulfilled,onRejected){if(this.status===“已完成”){onFulfilled(this.res);}if(this.status==="rejected"){onRejected(this.error);}//状态为pending时表示还没有被调用resolve或reject//将success函数和failure函数存放到这里对应的数组中,不执行操作只存放操作if(this.status==="pending"){this.resolveCallbacks.push(()=>onFulfilled(this.res));this.rejectCallbacks.push(()=>onRejected(this.error));}}}测试异步函数,打印结果中等待2秒后打印'executeresolve'newMyPromise((resolve,reject)=>{console.log("startexecution");setTimeout(()=>{resolve("executeresolve");},2000);}).then(res=>console.log(res));//输出结果如下://开始执行//执行resolve链式调用在这里实现异步操作!Hoho~但是,我们都知道Promise可以定义多个then,比如newPromise().then().then(),这是一个链式调用。当然,我们也需要实现这个功能。链式调用是指Promise完成后,开始执行下一个Promise。要实现这个功能,我们只需要在then()中返回Promise即可,说起来好像挺简单的。then()的实现思路:需要在then()中返回Promise对象,我们命名为nextPromise,仍然需要判断状态,并执行相应的处理。相应判断并返回classMyPromise{constructor(implement){...}then(onFulfilled,onRejected){//如果onRejected不是函数,直接抛出错误onFulfilled=typeofonFulfilled==="function"?onFulfilled:res=>res;onRejected=typeofonRejected===“功能”?onRejected:err=>{抛出错误;};constnextPromise=newMyPromise((resolve,reject)=>{if(this.status==="fulfilled"){//解决异步问题setTimeout(()=>{constx=onFulfilled(this.res);RecursionPromise(nextPromise,x,resolve,reject);},0);}if(this.status==="rejected"){setTimeout(()=>{constx=onRejected(this.error);RecursionPromise(nextProm是,x,解决,拒绝);},0);}if(this.status==="pending"){this.resolveCallbacks.push(()=>{setTimeout(()=>{constx=onFulfilled(this.res);RecursionPromise(nextPromise,x,resolve,拒绝);},0);});this.rejectCallbacks.push(()=>{setTimeout(()=>{constx=onRejected(this.error);RecursionPromise(nextPromise,x,resolve,reject);},0);});}});返回下一个承诺;通过的状态应该是resolve或reject。实现思路:nextPromise不能等于x,否则会一直调用自己判断x的类型。如果不是函数或对象,直接resolve(x)判断x是否有then(),如果then()是函数,则可以执行x的then(),回调标志的函数withsuccess和failure就是在执行x的then()时,success或者failure只能调用一次执行x的then(),success继续递归解析如果then()不是函数,直接resolve(x)函数RecursionPromise(nextPromise,x,resolve,reject){if(nextPromise===x)returnfalse;让旗帜;if(x!==null&&(typeofx==="object"||typeofx==="function")){try{letthen=x.then;if(typeofthen==="function"){then.call(x,y=>{if(flag)returnfalse;flag=true;//这说明Promise对象resolve后的结果仍然是Promise,然后继续递归解析RecursionPromise(nextPromise,y,resolve,reject);},error=>{if(flag)returnfalse;flag=true;reject(error);});}else{解决(x);}}catch(e){如果(标志)返回false;标志=真;拒绝(e);}}else{解决(x);公式调用的Promise实现了!还有一些方法这里就不一一实现了。毕竟一个完整的Promise的实现不是一篇文章可以说完的。有兴趣的同学可以参考Promise的功能对其进行解构和重写。如有不妥之处,请指出。公众号本文源码可在后台回复承诺获取。如果是转载文章,可以关注智商前端,回复承诺。写这篇文章的目的是给同学们提供一个函数解构的思路,学会分析一个函数的功能,进而解构每一步是如何执行和实现的。祝大家学习愉快,下次再见~结语??关注+点赞+收藏+评论+转发??,原创不易,鼓励作者创作更多优质文章关注公众号智商前端,一个专注CSS/JS开发技巧的前端公众号,更多前端干货等,关注你,免费回复素材。关注和回复群,拉你进技术交流群。欢迎关注智商前端。更多CSS/JS开发技巧只会推送在公众号