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

从头实现自己的Promise库

时间:2023-04-03 11:59:59 Node.js

刚开始写前端的时候,经常使用回调来处理异步请求,简单方便。后来写的时候放弃了callback,开始用promises来处理异步问题。Promise确实写起来比较漂亮,但是由于对其内部结构没有深入的了解,每次遇到一些复杂的情况,promise总是用起来不是那么顺手,而且debug需要很长时间。因此,本文将带大家从零开始,手写一个基本可用的promise。和我一起写下来,你就会对什么是promise及其内部结构有一个清晰的认识,以后可以在复杂的场景中使用promise。什么是Promise回文,什么是Promise?说白了,promise就是一个容器,里面装的是将来会结束的事件(通常是异步操作)的结果。首先,ES6规定Promise对象是一个用于生成Promise实例的构造函数。然后,这个构造函数接受一个函数(executor)作为参数,函数的两个参数是resolve和reject。最后,Promise实例生成后,可以使用then方法分别指定resolved状态和rejected状态的回调函数(onFulfilled和onRejected)。具体使用方法用代码表示如下:constpromise=newPromise(function(resolve,reject){//...somecodeif(/*异步操作成功*/){resolve(value);}else{拒绝(错误);}});promise.then(function(value){//成功},function(error){//失败});明白了这一点,我们就可以大胆的开始构建自己的promise了现在,我们给它起个名字:CutePromise实现了一个Promise:CutePromise我们直接使用ES6类来创建我们的CutePromise。如果你对ES6语法不熟悉,可以先看我另外两篇介绍ES6核心语法的文章。晚一点回来。30分钟掌握ES6/ES2015核心内容(上),30分钟掌握ES6/ES2015核心内容(下)classCutePromise{//executor是我们实例化CutePromise时传入的参数函数,它接受两个参数,这是解决和拒绝。//resolve和reject会在构造函数中定义,供executor调用constructor(executor){constresolve=()=>{}constreject=()=>{}executor(resolve,reject)}//提供一个then实例的方法,接收两个参数函数,//第一个参数函数必须传递,它会在promise完成后调用//第二个参数不需要,它会在promise失败(拒绝)后调用,它被称为then(onFulfilled,onRejected){}}在创建了我们的CutePromise之后,让我们弄清楚一个关键点:Promise对象的状态。Promise对象通过它们自己的状态来控制异步操作??。一个Promise实例有三种状态:异步操作挂起(pending),异步操作成功(fulfilled),异步操作失败(rejected)。以上三种状态中,fulfilled和rejected的组合称为resolved(已完成)。状态切换只有两条路径:一条是pending=>fulfilled,一条是pending=>rejected。状态一旦切换,就无法更改。现在让我们向CutePromise添加一个状态。大致的流程是:首先,在实例化的初始过程中,我们先将状态设置为PENDING,然后当执行器执行resolve时,将状态更改为FULFILLED,当执行器执行reject时,将状态更改为REJECTED。同时更新实例的值。构造函数(执行者){...this.state='PENDING';...constresolve=(result)=>{this.state='FULFILLED';this.value=结果;}constreject=(error)=>{this.state='REJECTED';this.value=错误;}...}让我们再看看then函数。then函数的两个参数,onFulfilled表示promise异步操作成功时调用的函数,onRejected表示promise异步操作失败时调用的函数。如果我们调用then的时候,promise已经被执行了(当任务是同步任务时),我们可以直接根据实例的状态执行相应的函数。如果promise的状态还是PENDING,那么我们直接把onFulfilled和onRejected存入变量chained中,等promise执行完再调用。构造函数(执行者){...this.state='PENDING';//chained用于存放promise执行后需要顺序调用的一系列函数。this.chained=[];constresolve=(result)=>{this.state='FULFILLED';this.value=结果;//promise已经执行成功,可以在.then()函数中依次调用onFulfilled函数for(const{onFulfilled}ofthis.chained){onFulfilled(res);}}...}then(onFulfilled,onRejected){if(this.state==='FULFILLED'){onFulfilled(this.value);}elseif(this.state==='REJECTED'){onRejected(this.value);}else{this.$chained.push({onFulfilled,onRejected});}}这样,我们就完成了一个CutePromise的创建。以下是完整的代码。您可以将代码复制到控制台进行测试这里:}this.state='PENDING';this.chained=[];constresolve=res=>{if(this.state!=='PENDING'){返回;}这个状态='已完成';this.internalValue=res;for(const{onFulfilled}ofthis.chained){onFulfilled(res);}};constreject=err=>{if(this.state!=='PENDING'){返回;}this.state='REJECTED';this.internalValue=错误;for(const{onRejected}ofthis.chained){onRejected(err);}};try{执行者(解决,拒绝);}抓住(错误){拒绝(错误);}}then(onFulfilled,onRejected){if(this.state==='FULFILLED'){onFulfilled(this.internalValue);}elseif(this.$state==='REJECTED'){onRejected(this.internalValue);}else{this.chained.push({onFulfilled,onRejected});}}}提供一下测试代码:letp=newCutePromise(resolve=>{setTimeout(()=>resolve('Hello'),100);});p.then(res=>console.log(res));p=newCutePromise((resolve,reject)=>{setTimeout(()=>reject(newError('woops')),100);});p.then(()=>{},err=>console.log('Asyncerror:',err.stack));p=newCutePromise(()=>{thrownewError('woops');});p.then(()=>{},err=>console.log('同步错误:',err.stack));实现链式调用实现链式调用其实很简单,我们只需要定义then()方法返回一个新的CutePromisethen(onFulfilled,onRejected){returnnewCutePromise((resolve,reject)=>{const_onFulfilled=res=>{try{//注意resolve可能要处理一个promiseresolve(onFulfilled(res));}catch(err){reject(err);}};const_onRejected=err=>{try{reject(onRejected(err));}catch(_err){reject(_err);}};如果(this.state==='FULFILLED'){_onFulfilled(this.internalValue);}elseif(this.state==='REJECTED'){_onRejected(this.internalValue);}else{this.chained.push({onFulfilled:_onFulfilled,onRejected:_onRejected});}});但是,我们还需要解决一个问题:如果then函数的第一个参数onfulfilled()本身返回了一个promise呢?比如下面这个用法其实是最真实的项目场景中最常见的:p=newCutePromise(resolve=>{setTimeout(()=>resolve('World'),100);});p.then(res=>newCutePromise(resolve=>resolve(`Hello,${res}`))).然后(res=>控制台日志(资源));所以我们需要让我们的resolve方法能够处理承诺:constresolve=res=>{if(this.state!=='PENDING'){return;}//假设res这个对象有一个then方法,那么我们认为res是一个promiseif(res!=null&&typeofres.then==='function'){returnres.then(resolve,reject);}...}三种方式想着promise数组的链式调用?Promise是如何做并发控制的?Promise是如何做异步缓存的?以上三个思考题其实跟你用不使用promise关系不大,但是如果你对promise没有深入的理解,这三个问题也不是那么容易解决的。参考资料:https://brunoscopelliti.com/l...